@ekkos/cli 0.2.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/dist/cache/LocalSessionStore.d.ts +129 -0
- package/dist/cache/LocalSessionStore.js +688 -0
- package/dist/cache/capture.d.ts +26 -0
- package/dist/cache/capture.js +461 -0
- package/dist/cache/index.d.ts +7 -0
- package/dist/cache/index.js +23 -0
- package/dist/cache/types.d.ts +147 -0
- package/dist/cache/types.js +40 -0
- package/dist/commands/init.d.ts +9 -0
- package/dist/commands/init.js +478 -0
- package/dist/commands/run.d.ts +12 -0
- package/dist/commands/run.js +829 -0
- package/dist/commands/setup.d.ts +6 -0
- package/dist/commands/setup.js +658 -0
- package/dist/commands/status.d.ts +1 -0
- package/dist/commands/status.js +109 -0
- package/dist/commands/test.d.ts +1 -0
- package/dist/commands/test.js +157 -0
- package/dist/deploy/agents.d.ts +15 -0
- package/dist/deploy/agents.js +72 -0
- package/dist/deploy/hooks.d.ts +16 -0
- package/dist/deploy/hooks.js +121 -0
- package/dist/deploy/index.d.ts +7 -0
- package/dist/deploy/index.js +24 -0
- package/dist/deploy/instructions.d.ts +12 -0
- package/dist/deploy/instructions.js +36 -0
- package/dist/deploy/mcp.d.ts +19 -0
- package/dist/deploy/mcp.js +109 -0
- package/dist/deploy/plugins.d.ts +19 -0
- package/dist/deploy/plugins.js +62 -0
- package/dist/deploy/settings.d.ts +8 -0
- package/dist/deploy/settings.js +84 -0
- package/dist/deploy/skills.d.ts +19 -0
- package/dist/deploy/skills.js +60 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +71 -0
- package/dist/restore/RestoreOrchestrator.d.ts +48 -0
- package/dist/restore/RestoreOrchestrator.js +481 -0
- package/dist/restore/index.d.ts +4 -0
- package/dist/restore/index.js +20 -0
- package/dist/utils/platform.d.ts +29 -0
- package/dist/utils/platform.js +65 -0
- package/dist/utils/session-words.json +119 -0
- package/dist/utils/state.d.ts +57 -0
- package/dist/utils/state.js +186 -0
- package/dist/utils/templates.d.ts +24 -0
- package/dist/utils/templates.js +118 -0
- package/package.json +48 -0
- package/templates/CLAUDE.md +287 -0
- package/templates/README.md +378 -0
- package/templates/agents/README.md +182 -0
- package/templates/agents/code-reviewer.md +166 -0
- package/templates/agents/debug-detective.md +169 -0
- package/templates/agents/ekkOS_Vercel.md +99 -0
- package/templates/agents/extension-manager.md +229 -0
- package/templates/agents/git-companion.md +185 -0
- package/templates/agents/github-test-agent.md +321 -0
- package/templates/agents/railway-manager.md +179 -0
- package/templates/claude-plugins/PHASE2_COMPLETION.md +346 -0
- package/templates/claude-plugins/PLUGIN_PROPOSALS.md +1776 -0
- package/templates/claude-plugins/README.md +587 -0
- package/templates/claude-plugins/agents/code-reviewer.json +14 -0
- package/templates/claude-plugins/agents/debug-detective.json +15 -0
- package/templates/claude-plugins/agents/git-companion.json +14 -0
- package/templates/claude-plugins/blog-manager/.claude-plugin/plugin.json +8 -0
- package/templates/claude-plugins/blog-manager/commands/blog.md +691 -0
- package/templates/claude-plugins/golden-loop-monitor/.claude-plugin/plugin.json +8 -0
- package/templates/claude-plugins/golden-loop-monitor/commands/loop-status.md +434 -0
- package/templates/claude-plugins/learning-tracker/.claude-plugin/plugin.json +8 -0
- package/templates/claude-plugins/learning-tracker/commands/my-patterns.md +282 -0
- package/templates/claude-plugins/memory-lens/.claude-plugin/plugin.json +8 -0
- package/templates/claude-plugins/memory-lens/commands/memory-search.md +181 -0
- package/templates/claude-plugins/pattern-coach/.claude-plugin/plugin.json +8 -0
- package/templates/claude-plugins/pattern-coach/commands/forge.md +365 -0
- package/templates/claude-plugins/project-schema-validator/.claude-plugin/plugin.json +8 -0
- package/templates/claude-plugins/project-schema-validator/commands/validate-schema.md +582 -0
- package/templates/claude-plugins-admin/AGENT_TEAM_PROPOSALS.md +819 -0
- package/templates/claude-plugins-admin/README.md +446 -0
- package/templates/claude-plugins-admin/autonomous-admin-agent/.claude-plugin/plugin.json +8 -0
- package/templates/claude-plugins-admin/autonomous-admin-agent/commands/agent.md +595 -0
- package/templates/claude-plugins-admin/backend-agent/.claude-plugin/plugin.json +8 -0
- package/templates/claude-plugins-admin/backend-agent/commands/backend.md +798 -0
- package/templates/claude-plugins-admin/deploy-guardian/.claude-plugin/plugin.json +8 -0
- package/templates/claude-plugins-admin/deploy-guardian/commands/deploy.md +554 -0
- package/templates/claude-plugins-admin/frontend-agent/.claude-plugin/plugin.json +8 -0
- package/templates/claude-plugins-admin/frontend-agent/commands/frontend.md +881 -0
- package/templates/claude-plugins-admin/mcp-server-manager/.claude-plugin/plugin.json +8 -0
- package/templates/claude-plugins-admin/mcp-server-manager/commands/mcp.md +85 -0
- package/templates/claude-plugins-admin/memory-system-monitor/.claude-plugin/plugin.json +8 -0
- package/templates/claude-plugins-admin/memory-system-monitor/commands/memory-health.md +569 -0
- package/templates/claude-plugins-admin/qa-agent/.claude-plugin/plugin.json +8 -0
- package/templates/claude-plugins-admin/qa-agent/commands/qa.md +863 -0
- package/templates/claude-plugins-admin/tech-lead-agent/.claude-plugin/plugin.json +8 -0
- package/templates/claude-plugins-admin/tech-lead-agent/commands/lead.md +732 -0
- package/templates/commands/continue.md +47 -0
- package/templates/cursor-hooks/after-agent-response.sh +117 -0
- package/templates/cursor-hooks/before-submit-prompt.sh +419 -0
- package/templates/cursor-hooks/hooks.json +20 -0
- package/templates/cursor-hooks/lib/contract.sh +320 -0
- package/templates/cursor-hooks/stop.sh +75 -0
- package/templates/cursor-rules/ekkos-memory.md +187 -0
- package/templates/hooks/assistant-response.sh +96 -0
- package/templates/hooks/hooks.json +28 -0
- package/templates/hooks/lib/contract.sh +320 -0
- package/templates/hooks/lib/state.sh +158 -0
- package/templates/hooks/session-start.ps1 +41 -0
- package/templates/hooks/session-start.sh +318 -0
- package/templates/hooks/stop.ps1 +16 -0
- package/templates/hooks/stop.sh +989 -0
- package/templates/hooks/user-prompt-submit.ps1 +174 -0
- package/templates/hooks/user-prompt-submit.sh +587 -0
- package/templates/hooks-node/lib/state.js +187 -0
- package/templates/hooks-node/stop.js +416 -0
- package/templates/hooks-node/user-prompt-submit.js +337 -0
- package/templates/plan-template.md +306 -0
- package/templates/rules/00-hooks-contract.mdc +89 -0
- package/templates/rules/30-ekkos-core.mdc +188 -0
- package/templates/rules/31-ekkos-messages.mdc +78 -0
- package/templates/skills/continue/SKILL.md +169 -0
- package/templates/skills/ekkOS_Deep_Recall/Skill.md +282 -0
- package/templates/skills/ekkOS_Learn/Skill.md +265 -0
- package/templates/skills/ekkOS_Memory_First/Skill.md +206 -0
- package/templates/skills/ekkOS_Plan_Assist/Skill.md +302 -0
- package/templates/skills/ekkOS_Preferences/Skill.md +247 -0
- package/templates/skills/ekkOS_Reflect/Skill.md +257 -0
- package/templates/skills/ekkOS_Safety/Skill.md +265 -0
- package/templates/skills/ekkOS_Schema/Skill.md +251 -0
- package/templates/skills/ekkOS_Summary/Skill.md +257 -0
- package/templates/skills/ekkOS_Vault/Skill.md +287 -0
- package/templates/skills/permissions/Skill.md +322 -0
- package/templates/spec-template.md +159 -0
- package/templates/windsurf-hooks/before-submit-prompt.sh +238 -0
- package/templates/windsurf-hooks/hooks.json +10 -0
- package/templates/windsurf-hooks/lib/contract.sh +320 -0
- package/templates/windsurf-rules/ekkos-memory.md +129 -0
|
@@ -0,0 +1,881 @@
|
|
|
1
|
+
# Frontend Agent
|
|
2
|
+
|
|
3
|
+
**ADMIN ONLY** - AI Frontend Developer specialized in React, Next.js 14+, TypeScript, Tailwind CSS, and modern UI patterns. Builds components, pages, and handles client-side logic.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The Frontend Agent is your React expert. It:
|
|
8
|
+
- Builds UI components following design system
|
|
9
|
+
- Creates Next.js pages with proper routing
|
|
10
|
+
- Implements state management (React Query, Zustand)
|
|
11
|
+
- Ensures accessibility (WCAG 2.1 AA)
|
|
12
|
+
- Optimizes performance (code splitting, lazy loading)
|
|
13
|
+
- Follows ekkOS component patterns
|
|
14
|
+
|
|
15
|
+
## Commands
|
|
16
|
+
|
|
17
|
+
### `/frontend build`
|
|
18
|
+
|
|
19
|
+
Build a new component, page, or feature.
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
/frontend build "Component/page description"
|
|
23
|
+
|
|
24
|
+
# Examples
|
|
25
|
+
/frontend build "Teams dashboard page"
|
|
26
|
+
/frontend build "Pattern card component"
|
|
27
|
+
/frontend build "User settings form with validation"
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
**What happens:**
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
🎨 Frontend Agent: Building "Teams Dashboard Page"
|
|
34
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
35
|
+
|
|
36
|
+
📊 Planning
|
|
37
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
38
|
+
|
|
39
|
+
Searching existing patterns...
|
|
40
|
+
✓ Found 3 similar dashboard components
|
|
41
|
+
✓ Found Teams API schema
|
|
42
|
+
✓ Checked design system (components/ui/*)
|
|
43
|
+
|
|
44
|
+
Component Breakdown:
|
|
45
|
+
• Layout wrapper (app layout)
|
|
46
|
+
• Teams list with search/filter
|
|
47
|
+
• Team card component
|
|
48
|
+
• Create team modal
|
|
49
|
+
• Empty state
|
|
50
|
+
|
|
51
|
+
Stack:
|
|
52
|
+
• Next.js 14 App Router
|
|
53
|
+
• TypeScript (strict mode)
|
|
54
|
+
• Tailwind CSS + Shadcn/ui
|
|
55
|
+
• React Query for data fetching
|
|
56
|
+
• Zod for validation
|
|
57
|
+
|
|
58
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
59
|
+
📝 Implementation
|
|
60
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
61
|
+
|
|
62
|
+
Creating app/teams/page.tsx...
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
import { Suspense } from 'react';
|
|
66
|
+
import { Metadata } from 'next';
|
|
67
|
+
import { TeamsHeader } from '@/components/teams/TeamsHeader';
|
|
68
|
+
import { TeamsList } from '@/components/teams/TeamsList';
|
|
69
|
+
import { CreateTeamButton } from '@/components/teams/CreateTeamButton';
|
|
70
|
+
import { PageContainer } from '@/components/layout/PageContainer';
|
|
71
|
+
|
|
72
|
+
export const metadata: Metadata = {
|
|
73
|
+
title: 'Teams | ekkOS',
|
|
74
|
+
description: 'Manage your teams and collaborate with others',
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export default async function TeamsPage() {
|
|
78
|
+
return (
|
|
79
|
+
<PageContainer>
|
|
80
|
+
<TeamsHeader>
|
|
81
|
+
<CreateTeamButton />
|
|
82
|
+
</TeamsHeader>
|
|
83
|
+
|
|
84
|
+
<Suspense fallback={<TeamsListSkeleton />}>
|
|
85
|
+
<TeamsList />
|
|
86
|
+
</Suspense>
|
|
87
|
+
</PageContainer>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Creating components/teams/TeamsList.tsx...
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
'use client';
|
|
96
|
+
|
|
97
|
+
import { useQuery } from '@tanstack/react-query';
|
|
98
|
+
import { useState } from 'react';
|
|
99
|
+
import { TeamCard } from './TeamCard';
|
|
100
|
+
import { TeamsEmptyState } from './TeamsEmptyState';
|
|
101
|
+
import { SearchInput } from '@/components/ui/SearchInput';
|
|
102
|
+
import { Spinner } from '@/components/ui/Spinner';
|
|
103
|
+
|
|
104
|
+
interface Team {
|
|
105
|
+
id: string;
|
|
106
|
+
name: string;
|
|
107
|
+
memberCount: number;
|
|
108
|
+
createdAt: string;
|
|
109
|
+
role: 'owner' | 'admin' | 'member';
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function TeamsList() {
|
|
113
|
+
const [search, setSearch] = useState('');
|
|
114
|
+
|
|
115
|
+
const { data: teams, isLoading, error } = useQuery({
|
|
116
|
+
queryKey: ['teams', search],
|
|
117
|
+
queryFn: async () => {
|
|
118
|
+
const response = await fetch(
|
|
119
|
+
`/api/teams?search=${encodeURIComponent(search)}`
|
|
120
|
+
);
|
|
121
|
+
if (!response.ok) throw new Error('Failed to fetch teams');
|
|
122
|
+
return response.json() as Promise<Team[]>;
|
|
123
|
+
},
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
if (isLoading) {
|
|
127
|
+
return (
|
|
128
|
+
<div className="flex items-center justify-center py-12">
|
|
129
|
+
<Spinner size="lg" />
|
|
130
|
+
<span className="ml-3 text-sm text-gray-500">Loading teams...</span>
|
|
131
|
+
</div>
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (error) {
|
|
136
|
+
return (
|
|
137
|
+
<div className="rounded-lg border border-red-200 bg-red-50 p-6">
|
|
138
|
+
<h3 className="text-sm font-medium text-red-800">
|
|
139
|
+
Failed to load teams
|
|
140
|
+
</h3>
|
|
141
|
+
<p className="mt-1 text-sm text-red-700">
|
|
142
|
+
{error instanceof Error ? error.message : 'Unknown error'}
|
|
143
|
+
</p>
|
|
144
|
+
</div>
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (!teams || teams.length === 0) {
|
|
149
|
+
return <TeamsEmptyState onCreateTeam={() => {/* handled by modal */}} />;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return (
|
|
153
|
+
<div className="space-y-6">
|
|
154
|
+
<SearchInput
|
|
155
|
+
value={search}
|
|
156
|
+
onChange={setSearch}
|
|
157
|
+
placeholder="Search teams..."
|
|
158
|
+
className="max-w-md"
|
|
159
|
+
/>
|
|
160
|
+
|
|
161
|
+
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
|
162
|
+
{teams.map((team) => (
|
|
163
|
+
<TeamCard key={team.id} team={team} />
|
|
164
|
+
))}
|
|
165
|
+
</div>
|
|
166
|
+
</div>
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Creating components/teams/TeamCard.tsx...
|
|
172
|
+
|
|
173
|
+
```typescript
|
|
174
|
+
'use client';
|
|
175
|
+
|
|
176
|
+
import Link from 'next/link';
|
|
177
|
+
import { Users, Crown, Shield } from 'lucide-react';
|
|
178
|
+
import { Card, CardHeader, CardTitle, CardDescription } from '@/components/ui/Card';
|
|
179
|
+
import { Badge } from '@/components/ui/Badge';
|
|
180
|
+
import { formatDistanceToNow } from 'date-fns';
|
|
181
|
+
|
|
182
|
+
interface TeamCardProps {
|
|
183
|
+
team: {
|
|
184
|
+
id: string;
|
|
185
|
+
name: string;
|
|
186
|
+
memberCount: number;
|
|
187
|
+
createdAt: string;
|
|
188
|
+
role: 'owner' | 'admin' | 'member';
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const roleIcons = {
|
|
193
|
+
owner: Crown,
|
|
194
|
+
admin: Shield,
|
|
195
|
+
member: Users,
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
const roleLabels = {
|
|
199
|
+
owner: 'Owner',
|
|
200
|
+
admin: 'Admin',
|
|
201
|
+
member: 'Member',
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
export function TeamCard({ team }: TeamCardProps) {
|
|
205
|
+
const RoleIcon = roleIcons[team.role];
|
|
206
|
+
|
|
207
|
+
return (
|
|
208
|
+
<Link href={`/teams/${team.id}`}>
|
|
209
|
+
<Card className="group hover:border-blue-300 hover:shadow-md transition-all">
|
|
210
|
+
<CardHeader>
|
|
211
|
+
<div className="flex items-start justify-between">
|
|
212
|
+
<CardTitle className="group-hover:text-blue-600 transition-colors">
|
|
213
|
+
{team.name}
|
|
214
|
+
</CardTitle>
|
|
215
|
+
<Badge variant={team.role === 'owner' ? 'default' : 'secondary'}>
|
|
216
|
+
<RoleIcon className="mr-1 h-3 w-3" />
|
|
217
|
+
{roleLabels[team.role]}
|
|
218
|
+
</Badge>
|
|
219
|
+
</div>
|
|
220
|
+
|
|
221
|
+
<CardDescription>
|
|
222
|
+
<div className="flex items-center gap-4 text-sm">
|
|
223
|
+
<span className="flex items-center gap-1">
|
|
224
|
+
<Users className="h-4 w-4" />
|
|
225
|
+
{team.memberCount} {team.memberCount === 1 ? 'member' : 'members'}
|
|
226
|
+
</span>
|
|
227
|
+
<span>
|
|
228
|
+
Created {formatDistanceToNow(new Date(team.createdAt), { addSuffix: true })}
|
|
229
|
+
</span>
|
|
230
|
+
</div>
|
|
231
|
+
</CardDescription>
|
|
232
|
+
</CardHeader>
|
|
233
|
+
</Card>
|
|
234
|
+
</Link>
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
Creating components/teams/CreateTeamButton.tsx...
|
|
240
|
+
|
|
241
|
+
```typescript
|
|
242
|
+
'use client';
|
|
243
|
+
|
|
244
|
+
import { useState } from 'react';
|
|
245
|
+
import { Plus } from 'lucide-react';
|
|
246
|
+
import { Button } from '@/components/ui/Button';
|
|
247
|
+
import { CreateTeamModal } from './CreateTeamModal';
|
|
248
|
+
|
|
249
|
+
export function CreateTeamButton() {
|
|
250
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
251
|
+
|
|
252
|
+
return (
|
|
253
|
+
<>
|
|
254
|
+
<Button onClick={() => setIsOpen(true)}>
|
|
255
|
+
<Plus className="mr-2 h-4 w-4" />
|
|
256
|
+
Create Team
|
|
257
|
+
</Button>
|
|
258
|
+
|
|
259
|
+
<CreateTeamModal
|
|
260
|
+
isOpen={isOpen}
|
|
261
|
+
onClose={() => setIsOpen(false)}
|
|
262
|
+
/>
|
|
263
|
+
</>
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
Creating components/teams/CreateTeamModal.tsx...
|
|
269
|
+
|
|
270
|
+
```typescript
|
|
271
|
+
'use client';
|
|
272
|
+
|
|
273
|
+
import { useState } from 'react';
|
|
274
|
+
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
|
275
|
+
import { useForm } from 'react-hook-form';
|
|
276
|
+
import { zodResolver } from '@hookform/resolvers/zod';
|
|
277
|
+
import { z } from 'zod';
|
|
278
|
+
import {
|
|
279
|
+
Dialog,
|
|
280
|
+
DialogContent,
|
|
281
|
+
DialogHeader,
|
|
282
|
+
DialogTitle,
|
|
283
|
+
DialogDescription,
|
|
284
|
+
DialogFooter,
|
|
285
|
+
} from '@/components/ui/Dialog';
|
|
286
|
+
import { Button } from '@/components/ui/Button';
|
|
287
|
+
import { Input } from '@/components/ui/Input';
|
|
288
|
+
import { Label } from '@/components/ui/Label';
|
|
289
|
+
import { toast } from 'sonner';
|
|
290
|
+
|
|
291
|
+
const createTeamSchema = z.object({
|
|
292
|
+
name: z.string()
|
|
293
|
+
.min(3, 'Team name must be at least 3 characters')
|
|
294
|
+
.max(50, 'Team name must be less than 50 characters')
|
|
295
|
+
.regex(/^[a-zA-Z0-9\s-]+$/, 'Team name can only contain letters, numbers, spaces, and hyphens'),
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
type CreateTeamForm = z.infer<typeof createTeamSchema>;
|
|
299
|
+
|
|
300
|
+
interface CreateTeamModalProps {
|
|
301
|
+
isOpen: boolean;
|
|
302
|
+
onClose: () => void;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
export function CreateTeamModal({ isOpen, onClose }: CreateTeamModalProps) {
|
|
306
|
+
const queryClient = useQueryClient();
|
|
307
|
+
|
|
308
|
+
const {
|
|
309
|
+
register,
|
|
310
|
+
handleSubmit,
|
|
311
|
+
formState: { errors },
|
|
312
|
+
reset,
|
|
313
|
+
} = useForm<CreateTeamForm>({
|
|
314
|
+
resolver: zodResolver(createTeamSchema),
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
const createTeamMutation = useMutation({
|
|
318
|
+
mutationFn: async (data: CreateTeamForm) => {
|
|
319
|
+
const response = await fetch('/api/teams', {
|
|
320
|
+
method: 'POST',
|
|
321
|
+
headers: { 'Content-Type': 'application/json' },
|
|
322
|
+
body: JSON.stringify(data),
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
if (!response.ok) {
|
|
326
|
+
const error = await response.json();
|
|
327
|
+
throw new Error(error.message || 'Failed to create team');
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
return response.json();
|
|
331
|
+
},
|
|
332
|
+
onSuccess: () => {
|
|
333
|
+
queryClient.invalidateQueries({ queryKey: ['teams'] });
|
|
334
|
+
toast.success('Team created successfully');
|
|
335
|
+
reset();
|
|
336
|
+
onClose();
|
|
337
|
+
},
|
|
338
|
+
onError: (error: Error) => {
|
|
339
|
+
toast.error(error.message || 'Failed to create team');
|
|
340
|
+
},
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
const onSubmit = (data: CreateTeamForm) => {
|
|
344
|
+
createTeamMutation.mutate(data);
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
return (
|
|
348
|
+
<Dialog open={isOpen} onOpenChange={onClose}>
|
|
349
|
+
<DialogContent>
|
|
350
|
+
<DialogHeader>
|
|
351
|
+
<DialogTitle>Create New Team</DialogTitle>
|
|
352
|
+
<DialogDescription>
|
|
353
|
+
Create a team to collaborate with others on patterns and projects.
|
|
354
|
+
</DialogDescription>
|
|
355
|
+
</DialogHeader>
|
|
356
|
+
|
|
357
|
+
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
|
|
358
|
+
<div className="space-y-2">
|
|
359
|
+
<Label htmlFor="name">Team Name</Label>
|
|
360
|
+
<Input
|
|
361
|
+
id="name"
|
|
362
|
+
{...register('name')}
|
|
363
|
+
placeholder="Engineering Team"
|
|
364
|
+
autoFocus
|
|
365
|
+
/>
|
|
366
|
+
{errors.name && (
|
|
367
|
+
<p className="text-sm text-red-600">{errors.name.message}</p>
|
|
368
|
+
)}
|
|
369
|
+
</div>
|
|
370
|
+
|
|
371
|
+
<DialogFooter>
|
|
372
|
+
<Button type="button" variant="outline" onClick={onClose}>
|
|
373
|
+
Cancel
|
|
374
|
+
</Button>
|
|
375
|
+
<Button type="submit" disabled={createTeamMutation.isPending}>
|
|
376
|
+
{createTeamMutation.isPending ? 'Creating...' : 'Create Team'}
|
|
377
|
+
</Button>
|
|
378
|
+
</DialogFooter>
|
|
379
|
+
</form>
|
|
380
|
+
</DialogContent>
|
|
381
|
+
</Dialog>
|
|
382
|
+
);
|
|
383
|
+
}
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
387
|
+
✅ Build Complete
|
|
388
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
389
|
+
|
|
390
|
+
Files Created: 6
|
|
391
|
+
✓ app/teams/page.tsx (main page)
|
|
392
|
+
✓ components/teams/TeamsList.tsx (list component)
|
|
393
|
+
✓ components/teams/TeamCard.tsx (card component)
|
|
394
|
+
✓ components/teams/CreateTeamButton.tsx (button)
|
|
395
|
+
✓ components/teams/CreateTeamModal.tsx (modal)
|
|
396
|
+
✓ components/teams/TeamsEmptyState.tsx (empty state)
|
|
397
|
+
|
|
398
|
+
Features Implemented:
|
|
399
|
+
✅ Server-side rendering (Next.js 14)
|
|
400
|
+
✅ Client-side data fetching (React Query)
|
|
401
|
+
✅ Search functionality
|
|
402
|
+
✅ Form validation (Zod)
|
|
403
|
+
✅ Error handling
|
|
404
|
+
✅ Loading states
|
|
405
|
+
✅ Empty states
|
|
406
|
+
✅ Responsive design (mobile-first)
|
|
407
|
+
✅ Accessibility (ARIA labels, keyboard nav)
|
|
408
|
+
✅ TypeScript strict mode
|
|
409
|
+
|
|
410
|
+
Performance:
|
|
411
|
+
✅ Code splitting (client components)
|
|
412
|
+
✅ Optimistic updates on mutations
|
|
413
|
+
✅ Automatic query invalidation
|
|
414
|
+
✅ Suspense boundaries
|
|
415
|
+
|
|
416
|
+
Next Steps:
|
|
417
|
+
• Add tests: /qa write "Teams UI"
|
|
418
|
+
• Review code: /lead review frontend
|
|
419
|
+
• Deploy to staging: /deploy execute apps/web --env staging
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
### `/frontend fix`
|
|
423
|
+
|
|
424
|
+
Fix bugs or issues in existing components.
|
|
425
|
+
|
|
426
|
+
```bash
|
|
427
|
+
/frontend fix "Issue description"
|
|
428
|
+
|
|
429
|
+
# Examples
|
|
430
|
+
/frontend fix "Teams list not paginating properly"
|
|
431
|
+
/frontend fix "Modal not closing on mobile"
|
|
432
|
+
/frontend fix "Add optimistic updates to team creation"
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
**What happens:**
|
|
436
|
+
|
|
437
|
+
```
|
|
438
|
+
🎨 Frontend Agent: Fixing "Teams list not paginating"
|
|
439
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
440
|
+
|
|
441
|
+
📊 Analyzing Issue
|
|
442
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
443
|
+
|
|
444
|
+
Reading components/teams/TeamsList.tsx...
|
|
445
|
+
|
|
446
|
+
Issue Found:
|
|
447
|
+
• Using client-side filtering only
|
|
448
|
+
• No pagination implementation
|
|
449
|
+
• Will break at scale (100+ teams)
|
|
450
|
+
|
|
451
|
+
Root Cause:
|
|
452
|
+
• API returns all teams at once
|
|
453
|
+
• No cursor/offset pagination
|
|
454
|
+
• Frontend doesn't request pages
|
|
455
|
+
|
|
456
|
+
Fix Strategy:
|
|
457
|
+
1. Update API to support pagination
|
|
458
|
+
2. Add infinite scroll to UI
|
|
459
|
+
3. Use React Query's useInfiniteQuery
|
|
460
|
+
|
|
461
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
462
|
+
📝 Implementing Fix
|
|
463
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
464
|
+
|
|
465
|
+
Updating components/teams/TeamsList.tsx...
|
|
466
|
+
|
|
467
|
+
```typescript
|
|
468
|
+
'use client';
|
|
469
|
+
|
|
470
|
+
import { useInfiniteQuery } from '@tanstack/react-query';
|
|
471
|
+
import { useEffect, useRef } from 'react';
|
|
472
|
+
import { TeamCard } from './TeamCard';
|
|
473
|
+
import { Spinner } from '@/components/ui/Spinner';
|
|
474
|
+
|
|
475
|
+
const PAGE_SIZE = 12;
|
|
476
|
+
|
|
477
|
+
export function TeamsList() {
|
|
478
|
+
const observerTarget = useRef<HTMLDivElement>(null);
|
|
479
|
+
|
|
480
|
+
const {
|
|
481
|
+
data,
|
|
482
|
+
fetchNextPage,
|
|
483
|
+
hasNextPage,
|
|
484
|
+
isFetchingNextPage,
|
|
485
|
+
isLoading,
|
|
486
|
+
} = useInfiniteQuery({
|
|
487
|
+
queryKey: ['teams'],
|
|
488
|
+
queryFn: async ({ pageParam = 0 }) => {
|
|
489
|
+
const response = await fetch(
|
|
490
|
+
`/api/teams?offset=${pageParam}&limit=${PAGE_SIZE}`
|
|
491
|
+
);
|
|
492
|
+
if (!response.ok) throw new Error('Failed to fetch teams');
|
|
493
|
+
return response.json();
|
|
494
|
+
},
|
|
495
|
+
getNextPageParam: (lastPage, pages) => {
|
|
496
|
+
if (lastPage.length < PAGE_SIZE) return undefined;
|
|
497
|
+
return pages.length * PAGE_SIZE;
|
|
498
|
+
},
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
// Infinite scroll observer
|
|
502
|
+
useEffect(() => {
|
|
503
|
+
const observer = new IntersectionObserver(
|
|
504
|
+
(entries) => {
|
|
505
|
+
if (entries[0].isIntersecting && hasNextPage && !isFetchingNextPage) {
|
|
506
|
+
fetchNextPage();
|
|
507
|
+
}
|
|
508
|
+
},
|
|
509
|
+
{ threshold: 0.1 }
|
|
510
|
+
);
|
|
511
|
+
|
|
512
|
+
const target = observerTarget.current;
|
|
513
|
+
if (target) observer.observe(target);
|
|
514
|
+
|
|
515
|
+
return () => {
|
|
516
|
+
if (target) observer.unobserve(target);
|
|
517
|
+
};
|
|
518
|
+
}, [hasNextPage, isFetchingNextPage, fetchNextPage]);
|
|
519
|
+
|
|
520
|
+
const teams = data?.pages.flatMap((page) => page) ?? [];
|
|
521
|
+
|
|
522
|
+
if (isLoading) {
|
|
523
|
+
return <div>Loading...</div>;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
return (
|
|
527
|
+
<div className="space-y-6">
|
|
528
|
+
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
|
529
|
+
{teams.map((team) => (
|
|
530
|
+
<TeamCard key={team.id} team={team} />
|
|
531
|
+
))}
|
|
532
|
+
</div>
|
|
533
|
+
|
|
534
|
+
{/* Intersection observer target */}
|
|
535
|
+
<div ref={observerTarget} className="flex justify-center py-4">
|
|
536
|
+
{isFetchingNextPage && <Spinner />}
|
|
537
|
+
</div>
|
|
538
|
+
|
|
539
|
+
{!hasNextPage && teams.length > 0 && (
|
|
540
|
+
<p className="text-center text-sm text-gray-500">
|
|
541
|
+
All teams loaded
|
|
542
|
+
</p>
|
|
543
|
+
)}
|
|
544
|
+
</div>
|
|
545
|
+
);
|
|
546
|
+
}
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
550
|
+
✅ Fix Complete
|
|
551
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
552
|
+
|
|
553
|
+
Changes:
|
|
554
|
+
✓ Replaced useQuery with useInfiniteQuery
|
|
555
|
+
✓ Added Intersection Observer for infinite scroll
|
|
556
|
+
✓ Proper cleanup in useEffect
|
|
557
|
+
✓ Loading states for next pages
|
|
558
|
+
✓ "All loaded" message
|
|
559
|
+
|
|
560
|
+
Performance:
|
|
561
|
+
✓ Only loads 12 teams initially
|
|
562
|
+
✓ Loads next page when scrolling near bottom
|
|
563
|
+
✓ No unnecessary re-fetches
|
|
564
|
+
|
|
565
|
+
Note: Requires backend API update:
|
|
566
|
+
Backend Agent should add pagination support to /api/teams
|
|
567
|
+
Command: /backend fix "Add pagination to Teams API"
|
|
568
|
+
|
|
569
|
+
Testing:
|
|
570
|
+
✓ Manual test: Works with 100+ teams
|
|
571
|
+
✓ Network throttling: Smooth loading
|
|
572
|
+
✓ Mobile: Smooth scroll on touch devices
|
|
573
|
+
```
|
|
574
|
+
|
|
575
|
+
### `/frontend optimize`
|
|
576
|
+
|
|
577
|
+
Optimize performance of existing components.
|
|
578
|
+
|
|
579
|
+
```bash
|
|
580
|
+
/frontend optimize "Component name or path"
|
|
581
|
+
|
|
582
|
+
# Examples
|
|
583
|
+
/frontend optimize "Dashboard page"
|
|
584
|
+
/frontend optimize "Pattern search component"
|
|
585
|
+
/frontend optimize "app/teams/page.tsx"
|
|
586
|
+
```
|
|
587
|
+
|
|
588
|
+
**What happens:**
|
|
589
|
+
|
|
590
|
+
```
|
|
591
|
+
🎨 Frontend Agent: Optimizing "Dashboard Page"
|
|
592
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
593
|
+
|
|
594
|
+
📊 Performance Analysis
|
|
595
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
596
|
+
|
|
597
|
+
Analyzing app/dashboard/page.tsx...
|
|
598
|
+
|
|
599
|
+
Current Metrics:
|
|
600
|
+
• Initial Load: 2.1s (🔴 Slow)
|
|
601
|
+
• First Contentful Paint: 1.8s
|
|
602
|
+
• Time to Interactive: 2.3s
|
|
603
|
+
• Bundle Size: 245 KB (🟡 Large)
|
|
604
|
+
|
|
605
|
+
Issues Found:
|
|
606
|
+
1. 🔴 All components loaded eagerly (no code splitting)
|
|
607
|
+
2. 🟡 Large chart library (recharts) in main bundle
|
|
608
|
+
3. 🟡 No memoization on expensive calculations
|
|
609
|
+
4. 🟢 Images already optimized (next/image)
|
|
610
|
+
|
|
611
|
+
Optimization Plan:
|
|
612
|
+
1. Lazy load chart components
|
|
613
|
+
2. Memoize pattern stats calculation
|
|
614
|
+
3. Defer non-critical data fetching
|
|
615
|
+
4. Add loading skeletons
|
|
616
|
+
|
|
617
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
618
|
+
📝 Implementing Optimizations
|
|
619
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
620
|
+
|
|
621
|
+
Updating app/dashboard/page.tsx...
|
|
622
|
+
|
|
623
|
+
```typescript
|
|
624
|
+
import { Suspense, lazy } from 'react';
|
|
625
|
+
import { DashboardHeader } from '@/components/dashboard/DashboardHeader';
|
|
626
|
+
import { QuickStats } from '@/components/dashboard/QuickStats';
|
|
627
|
+
import { DashboardSkeleton } from '@/components/dashboard/DashboardSkeleton';
|
|
628
|
+
|
|
629
|
+
// Lazy load heavy components
|
|
630
|
+
const PatternChart = lazy(() => import('@/components/dashboard/PatternChart'));
|
|
631
|
+
const RecentActivity = lazy(() => import('@/components/dashboard/RecentActivity'));
|
|
632
|
+
|
|
633
|
+
export default function DashboardPage() {
|
|
634
|
+
return (
|
|
635
|
+
<div className="space-y-6">
|
|
636
|
+
<DashboardHeader />
|
|
637
|
+
|
|
638
|
+
{/* Load immediately (small, critical) */}
|
|
639
|
+
<QuickStats />
|
|
640
|
+
|
|
641
|
+
{/* Lazy load with suspense (large, non-critical) */}
|
|
642
|
+
<Suspense fallback={<DashboardSkeleton variant="chart" />}>
|
|
643
|
+
<PatternChart />
|
|
644
|
+
</Suspense>
|
|
645
|
+
|
|
646
|
+
<Suspense fallback={<DashboardSkeleton variant="list" />}>
|
|
647
|
+
<RecentActivity />
|
|
648
|
+
</Suspense>
|
|
649
|
+
</div>
|
|
650
|
+
);
|
|
651
|
+
}
|
|
652
|
+
```
|
|
653
|
+
|
|
654
|
+
Updating components/dashboard/QuickStats.tsx...
|
|
655
|
+
|
|
656
|
+
```typescript
|
|
657
|
+
'use client';
|
|
658
|
+
|
|
659
|
+
import { useMemo } from 'react';
|
|
660
|
+
import { useQuery } from '@tanstack/react-query';
|
|
661
|
+
import { StatCard } from '@/components/ui/StatCard';
|
|
662
|
+
|
|
663
|
+
export function QuickStats() {
|
|
664
|
+
const { data: stats } = useQuery({
|
|
665
|
+
queryKey: ['dashboard-stats'],
|
|
666
|
+
queryFn: fetchDashboardStats,
|
|
667
|
+
staleTime: 5 * 60 * 1000, // Cache for 5 minutes
|
|
668
|
+
});
|
|
669
|
+
|
|
670
|
+
// Memoize expensive calculations
|
|
671
|
+
const metrics = useMemo(() => {
|
|
672
|
+
if (!stats) return null;
|
|
673
|
+
|
|
674
|
+
return {
|
|
675
|
+
totalPatterns: stats.patterns.length,
|
|
676
|
+
successRate: calculateSuccessRate(stats.patterns),
|
|
677
|
+
avgResponseTime: calculateAvgResponseTime(stats.patterns),
|
|
678
|
+
topTags: findTopTags(stats.patterns, 5),
|
|
679
|
+
};
|
|
680
|
+
}, [stats]);
|
|
681
|
+
|
|
682
|
+
if (!metrics) return <QuickStatsSkeleton />;
|
|
683
|
+
|
|
684
|
+
return (
|
|
685
|
+
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
|
|
686
|
+
<StatCard
|
|
687
|
+
title="Total Patterns"
|
|
688
|
+
value={metrics.totalPatterns}
|
|
689
|
+
trend="+12%"
|
|
690
|
+
/>
|
|
691
|
+
<StatCard
|
|
692
|
+
title="Success Rate"
|
|
693
|
+
value={`${metrics.successRate}%`}
|
|
694
|
+
trend="+5%"
|
|
695
|
+
/>
|
|
696
|
+
{/* ... more stats */}
|
|
697
|
+
</div>
|
|
698
|
+
);
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
// Memoize calculation functions
|
|
702
|
+
const calculateSuccessRate = (patterns: Pattern[]) => {
|
|
703
|
+
const successful = patterns.filter(p => p.outcome === 'success').length;
|
|
704
|
+
return Math.round((successful / patterns.length) * 100);
|
|
705
|
+
};
|
|
706
|
+
```
|
|
707
|
+
|
|
708
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
709
|
+
✅ Optimization Complete
|
|
710
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
711
|
+
|
|
712
|
+
Changes:
|
|
713
|
+
✓ Lazy loaded PatternChart (-180 KB from initial bundle)
|
|
714
|
+
✓ Lazy loaded RecentActivity (-45 KB from initial bundle)
|
|
715
|
+
✓ Memoized expensive calculations
|
|
716
|
+
✓ Added query staleTime caching
|
|
717
|
+
✓ Added loading skeletons
|
|
718
|
+
|
|
719
|
+
Performance Improvements:
|
|
720
|
+
Initial Load: 2.1s → 0.9s (57% faster) ✅
|
|
721
|
+
FCP: 1.8s → 0.7s ✅
|
|
722
|
+
TTI: 2.3s → 1.1s ✅
|
|
723
|
+
Bundle Size: 245 KB → 20 KB initial (225 KB lazy loaded) ✅
|
|
724
|
+
|
|
725
|
+
Lighthouse Score:
|
|
726
|
+
Performance: 72 → 96 (+24 points) 🎉
|
|
727
|
+
Best Practices: 100 → 100
|
|
728
|
+
Accessibility: 95 → 95
|
|
729
|
+
SEO: 100 → 100
|
|
730
|
+
|
|
731
|
+
Next Steps:
|
|
732
|
+
• Test on slow 3G: Verify loading experience
|
|
733
|
+
• Monitor Core Web Vitals in production
|
|
734
|
+
• Consider adding service worker for offline support
|
|
735
|
+
```
|
|
736
|
+
|
|
737
|
+
### `/frontend review`
|
|
738
|
+
|
|
739
|
+
Review component code quality and suggest improvements.
|
|
740
|
+
|
|
741
|
+
```bash
|
|
742
|
+
/frontend review "Component path"
|
|
743
|
+
|
|
744
|
+
# Examples
|
|
745
|
+
/frontend review "components/teams/TeamsList.tsx"
|
|
746
|
+
/frontend review "app/teams/**"
|
|
747
|
+
```
|
|
748
|
+
|
|
749
|
+
## MCP Tools Used
|
|
750
|
+
|
|
751
|
+
The Frontend Agent uses:
|
|
752
|
+
|
|
753
|
+
- `ekkOS_Search` - Find similar UI patterns and components
|
|
754
|
+
- `ekkOS_Codebase` - Search existing component implementations
|
|
755
|
+
- `ekkOS_IndexSchema` - Get TypeScript types for props
|
|
756
|
+
- `ekkOS_GetSchema` - Verify API response shapes
|
|
757
|
+
- `ekkOS_Forge` - Save new component patterns
|
|
758
|
+
- `Read` - Read existing component files
|
|
759
|
+
- `Edit` - Update components
|
|
760
|
+
- `Write` - Create new components
|
|
761
|
+
- `Glob` - Find related component files
|
|
762
|
+
- `Grep` - Search for component usage
|
|
763
|
+
|
|
764
|
+
## Best Practices
|
|
765
|
+
|
|
766
|
+
### Follow ekkOS Component Patterns
|
|
767
|
+
|
|
768
|
+
The agent follows established patterns:
|
|
769
|
+
|
|
770
|
+
```typescript
|
|
771
|
+
// ✅ Good: Follows ekkOS patterns
|
|
772
|
+
'use client';
|
|
773
|
+
|
|
774
|
+
import { useQuery } from '@tanstack/react-query';
|
|
775
|
+
import { Card } from '@/components/ui/Card';
|
|
776
|
+
|
|
777
|
+
export function TeamsList() {
|
|
778
|
+
const { data, isLoading, error } = useQuery({
|
|
779
|
+
queryKey: ['teams'],
|
|
780
|
+
queryFn: fetchTeams,
|
|
781
|
+
});
|
|
782
|
+
|
|
783
|
+
// Proper error handling
|
|
784
|
+
if (error) return <ErrorState error={error} />;
|
|
785
|
+
|
|
786
|
+
// Proper loading state
|
|
787
|
+
if (isLoading) return <Skeleton />;
|
|
788
|
+
|
|
789
|
+
return <div>{/* ... */}</div>;
|
|
790
|
+
}
|
|
791
|
+
```
|
|
792
|
+
|
|
793
|
+
```typescript
|
|
794
|
+
// ❌ Bad: Missing error handling
|
|
795
|
+
export function TeamsList() {
|
|
796
|
+
const { data } = useQuery({ queryKey: ['teams'], queryFn: fetchTeams });
|
|
797
|
+
|
|
798
|
+
return <div>{data.map(/* ... */)}</div>; // Will crash if data is undefined
|
|
799
|
+
}
|
|
800
|
+
```
|
|
801
|
+
|
|
802
|
+
### Use Design System Components
|
|
803
|
+
|
|
804
|
+
```typescript
|
|
805
|
+
// ✅ Good: Uses Shadcn/ui components
|
|
806
|
+
import { Button } from '@/components/ui/Button';
|
|
807
|
+
import { Card } from '@/components/ui/Card';
|
|
808
|
+
|
|
809
|
+
// ❌ Bad: Custom styling inline
|
|
810
|
+
<button className="bg-blue-500 hover:bg-blue-600 px-4 py-2 rounded">
|
|
811
|
+
Click me
|
|
812
|
+
</button>
|
|
813
|
+
```
|
|
814
|
+
|
|
815
|
+
### Ensure Accessibility
|
|
816
|
+
|
|
817
|
+
```typescript
|
|
818
|
+
// ✅ Good: Accessible
|
|
819
|
+
<button
|
|
820
|
+
onClick={handleClick}
|
|
821
|
+
aria-label="Close modal"
|
|
822
|
+
aria-pressed={isPressed}
|
|
823
|
+
>
|
|
824
|
+
<X className="h-4 w-4" />
|
|
825
|
+
<span className="sr-only">Close</span>
|
|
826
|
+
</button>
|
|
827
|
+
|
|
828
|
+
// ❌ Bad: Not accessible
|
|
829
|
+
<div onClick={handleClick}>
|
|
830
|
+
<X />
|
|
831
|
+
</div>
|
|
832
|
+
```
|
|
833
|
+
|
|
834
|
+
## Integration with Other Agents
|
|
835
|
+
|
|
836
|
+
Frontend Agent works closely with:
|
|
837
|
+
|
|
838
|
+
- **Backend Agent** - Consumes APIs, validates response shapes
|
|
839
|
+
- **QA Agent** - Receives component tests
|
|
840
|
+
- **Tech Lead** - Gets assignments and reviews
|
|
841
|
+
|
|
842
|
+
## Troubleshooting
|
|
843
|
+
|
|
844
|
+
### Component Not Rendering
|
|
845
|
+
|
|
846
|
+
**Problem:** Component shows blank or errors
|
|
847
|
+
**Check:** Browser console for React errors
|
|
848
|
+
**Fix:** Agent reviews component and fixes issues
|
|
849
|
+
|
|
850
|
+
### Styling Not Applied
|
|
851
|
+
|
|
852
|
+
**Problem:** Tailwind classes not working
|
|
853
|
+
**Check:** Verify classes in tailwind.config.ts
|
|
854
|
+
**Fix:** Agent updates config or uses correct classes
|
|
855
|
+
|
|
856
|
+
### State Not Updating
|
|
857
|
+
|
|
858
|
+
**Problem:** React Query data stale
|
|
859
|
+
**Check:** Query invalidation logic
|
|
860
|
+
**Fix:** Agent adds proper invalidation
|
|
861
|
+
|
|
862
|
+
---
|
|
863
|
+
|
|
864
|
+
## Summary
|
|
865
|
+
|
|
866
|
+
The Frontend Agent is your React expert that:
|
|
867
|
+
|
|
868
|
+
✅ **Builds** - Components, pages, and features
|
|
869
|
+
✅ **Fixes** - Bugs and UI issues
|
|
870
|
+
✅ **Optimizes** - Performance and bundle size
|
|
871
|
+
✅ **Reviews** - Code quality and patterns
|
|
872
|
+
|
|
873
|
+
**Ship beautiful UIs faster.**
|
|
874
|
+
|
|
875
|
+
```bash
|
|
876
|
+
/frontend build "Your component here"
|
|
877
|
+
```
|
|
878
|
+
|
|
879
|
+
---
|
|
880
|
+
|
|
881
|
+
**Build fast. Look good. Work everywhere.** 🎨
|