opencode-agent-kit
Version:
Multi-stack OpenCode agent toolkit — 33+ specialized AI agents, 200+ skills, 46 commands, 8 MCP servers (Nuxt, React, Node.js, Laravel, CI3, Android, Flutter, DevOps, SEO, SonarQube, and more)
957 lines (725 loc) • 21.4 kB
Markdown
# Frontend Developer Agent - Contoh Praktis
Dokumen ini berisi contoh-contoh praktis penggunaan Frontend Developer Agent untuk berbagai skenario development.
## Daftar Isi
1. [Component Development](#1-component-development)
2. [Nuxt UI Integration](#2-nuxt-ui-integration)
3. [API Integration](#3-api-integration)
4. [Form Handling](#4-form-handling)
5. [State Management](#5-state-management)
6. [Performance Optimization](#6-performance-optimization)
7. [Accessibility Review](#7-accessibility-review)
8. [Bug Fixing](#8-bug-fixing)
---
## 1. Component Development
### Skenario: Membuat Product Card Component
**Prompt:**
```
@frontend Saya butuh ProductCard component untuk e-commerce dengan fitur:
- Gambar produk dengan lazy loading
- Badge untuk discount
- Rating stars
- Add to cart button
- Wishlist toggle
- Support dark mode
- Accessible (WCAG 2.1)
Gunakan Nuxt UI components sebisa mungkin.
```
**Output yang Diharapkan:**
- `app/components/product/ProductCard.vue` menggunakan `<UCard>`, `<UBadge>`, `<UButton>`
- TypeScript interfaces untuk Product type
- Composable `useProduct` untuk business logic
- Accessible markup dengan ARIA labels
- Responsive design dengan Tailwind
- Unit tests dengan Vitest
**Agent akan:**
1. ✅ Check MCP Nuxt UI untuk available components
2. ✅ Load skill `nuxt-ui` untuk component patterns
3. ✅ Load skill `building-components` untuk best practices
4. ✅ Load skill `web-design-guidelines` untuk accessibility
5. ✅ Create component dengan TypeScript strict mode
6. ✅ Add comprehensive tests
---
## 2. Nuxt UI Integration
### Skenario: Dashboard Layout dengan Sidebar
**Prompt:**
```
@frontend Build dashboard layout dengan:
- Collapsible sidebar navigation
- Top header dengan user menu
- Breadcrumb navigation
- Main content area
- Toast notifications
- Command palette (Cmd+K)
Gunakan Nuxt UI App components.
```
**Output yang Diharapkan:**
- Layout menggunakan `<UDashboard>` components
- `<UVerticalNavigation>` untuk sidebar
- `<UCommandPalette>` untuk search
- `<UToast>` untuk notifications
- Dark mode toggle
- Responsive mobile menu
**Agent akan:**
1. ✅ Query Nuxt UI MCP untuk Dashboard components
2. ✅ Check documentation untuk `<UDashboard>` API
3. ✅ Implement dengan Nuxt 4 app/ directory structure
4. ✅ Add keyboard shortcuts
5. ✅ Test responsive behavior
---
## 3. API Integration
### Skenario: Data Fetching dengan Error Handling
**Prompt:**
```
@frontend Implement user list page dengan:
- Fetch dari /api/users
- Loading skeleton
- Error state dengan retry
- Pagination
- Search dan filter
- Optimistic updates untuk delete
Gunakan useApi composable (app/composables/useApi.ts)
```
**Output yang Diharapkan:**
- `app/pages/users/index.vue` dengan `useApi`
- Error handling dengan `<UAlert>`
- Loading states dengan `<USkeleton>`
- Pagination dengan `<UPagination>`
- Search dengan debounce
**Agent akan:**
1. ✅ Load skill `frontend-patterns` untuk data fetching
2. ✅ Use custom `useApi` composable (recommended pattern)
3. ✅ Implement error boundaries
4. ✅ Add loading states
5. ✅ Handle authentication automatically
**Code Example (Recommended - Using useApi):**
```vue
<script setup lang="ts">
interface User {
id: string
name: string
email: string
role: 'admin' | 'user'
}
const page = ref(1)
const search = ref('')
// ✅ BEST: Use useApi for automatic auth + error handling
const {
data: response,
pending,
error,
refresh,
} = await useApi<User[]>('/users', {
query: { page, search },
watch: [page, search],
})
// Access users from response.data
const users = computed(() => response.value?.data || [])
const handleDelete = async (id: string) => {
// Optimistic update
const originalUsers = users.value
users.value = users.value.filter((u) => u.id !== id)
// Use useApi for delete
const { error: deleteError } = await useApi(`/users/${id}`, {
method: 'DELETE',
})
if (deleteError.value) {
// Rollback on error
users.value = originalUsers
useToast().add({
title: 'Error',
description: deleteError.value.message,
color: 'red',
})
} else {
useToast().add({
title: 'Success',
description: 'User deleted successfully',
})
}
}
</script>
<template>
<UContainer>
<div class="space-y-4">
<UInput v-model="search" placeholder="Search users..." />
<div v-if="pending">
<USkeleton v-for="i in 5" :key="i" class="h-20" />
</div>
<UAlert v-else-if="error" color="red" :description="error.message">
<template #actions>
<UButton @click="refresh">Retry</UButton>
</template>
</UAlert>
<div v-else>
<UCard v-for="user in users" :key="user.id">
<template #header>
<div class="flex items-center justify-between">
<div>
<h3 class="font-semibold">{{ user.name }}</h3>
<p class="text-sm text-gray-500">{{ user.email }}</p>
</div>
<UBadge :color="user.role === 'admin' ? 'blue' : 'gray'">
{{ user.role }}
</UBadge>
</div>
</template>
<template #footer>
<UButton color="red" variant="ghost" @click="handleDelete(user.id)"> Delete </UButton>
</template>
</UCard>
</div>
<UPagination v-model="page" :total="100" />
</div>
</UContainer>
</template>
```
**Alternative (Using native useFetch):**
```vue
<script setup lang="ts">
// ⚠️ Only use this if you need custom caching or external API
const {
data: users,
pending,
error,
refresh,
} = await useFetch<User[]>('/api/users', {
query: { page, search },
watch: [page, search],
})
</script>
```
**Key Differences - useApi vs useFetch:**
| Feature | useApi | useFetch |
| --------------- | ------------------------ | -------------------- |
| Authentication | ✅ Automatic | ⚠️ Manual |
| Error message | ✅ `error.value.message` | ❌ Need to extract |
| 401 Redirect | ✅ Automatic | ❌ Manual |
| SSR/CSR Smart | ✅ Yes | ✅ Yes |
| Type Safety | ✅ Generic `<T>` | ✅ Generic `<T>` |
| POST/PUT/DELETE | ✅ Auto-$fetch | ⚠️ Need useAsyncData |
**IMPORTANT: Always prefer `useApi` for this project's API calls.**
@frontend Implement user list page dengan:
- Fetch dari /api/users
- Loading skeleton
- Error state dengan retry
- Pagination
- Search dan filter
- Optimistic updates untuk delete
````
**Output yang Diharapkan:**
- `app/pages/users/index.vue` dengan `useFetch`
- Error handling dengan `<UAlert>`
- Loading states dengan `<USkeleton>`
- Pagination dengan `<UPagination>`
- Search dengan debounce
**Agent akan:**
1. ✅ Load skill `frontend-patterns` untuk data fetching
2. ✅ Use Nuxt 4 `useFetch` dengan proper typing
3. ✅ Implement error boundaries
4. ✅ Add loading states
5. ✅ Create reusable composable
**Code Example:**
```vue
<script setup lang="ts">
interface User {
id: string
name: string
email: string
role: 'admin' | 'user'
}
const page = ref(1)
const search = ref('')
const {
data: users,
pending,
error,
refresh,
} = await useFetch<User[]>('/api/users', {
query: { page, search },
watch: [page, search],
})
const handleDelete = async (id: string) => {
// Optimistic update
users.value = users.value?.filter((u) => u.id !== id)
try {
await $fetch(`/api/users/${id}`, { method: 'DELETE' })
} catch (err) {
// Rollback on error
await refresh()
}
}
</script>
<template>
<UContainer>
<div class="space-y-4">
<UInput v-model="search" placeholder="Search users..." />
<div v-if="pending">
<USkeleton v-for="i in 5" :key="i" class="h-20" />
</div>
<UAlert v-else-if="error" color="red" :description="error.message">
<template #actions>
<UButton @click="refresh">Retry</UButton>
</template>
</UAlert>
<div v-else>
<UCard v-for="user in users" :key="user.id">
<!-- User content -->
</UCard>
</div>
<UPagination v-model="page" :total="100" />
</div>
</UContainer>
</template>
````
---
## 4. Form Handling
### Skenario: Complex Form dengan Validation
**Prompt:**
```
@frontend Create user profile form dengan:
- Multiple steps (Personal, Contact, Preferences)
- Real-time validation
- File upload untuk avatar
- Auto-save draft
- Success/error toast
- Accessible form labels
```
**Output yang Diharapkan:**
- Multi-step form dengan `<UForm>`
- Validation dengan Zod schema
- `<UFormGroup>` untuk field groups
- File upload dengan preview
- Auto-save dengan debounce
**Agent akan:**
1. ✅ Check Nuxt UI Form components via MCP
2. ✅ Load skill `frontend-patterns` untuk form handling
3. ✅ Implement validation dengan Zod
4. ✅ Add accessibility features
5. ✅ Create composable untuk form state
**Code Example:**
```vue
<script setup lang="ts">
import { z } from 'zod'
const schema = z.object({
name: z.string().min(2, 'Name must be at least 2 characters'),
email: z.string().email('Invalid email address'),
avatar: z.instanceof(File).optional(),
})
type FormData = z.infer<typeof schema>
const state = reactive<FormData>({
name: '',
email: '',
avatar: undefined,
})
const { pending, error, execute } = useFetch('/api/profile', {
method: 'POST',
body: state,
immediate: false,
})
const onSubmit = async () => {
await execute()
if (!error.value) {
useToast().add({ title: 'Profile updated!' })
}
}
// Auto-save draft
watchDebounced(
state,
() => {
localStorage.setItem('profile-draft', JSON.stringify(state))
},
{ debounce: 1000 }
)
</script>
<template>
<UForm :schema="schema" :state="state" @submit="onSubmit">
<UFormGroup label="Name" name="name" required>
<UInput v-model="state.name" />
</UFormGroup>
<UFormGroup label="Email" name="email" required>
<UInput v-model="state.email" type="email" />
</UFormGroup>
<UFormGroup label="Avatar" name="avatar">
<UInput type="file" accept="image/*" />
</UFormGroup>
<UButton type="submit" :loading="pending"> Save Profile </UButton>
</UForm>
</template>
```
---
## 5. State Management
### Skenario: Shopping Cart dengan Pinia
**Prompt:**
```
@frontend Implement shopping cart dengan:
- Add/remove items
- Update quantity
- Calculate total
- Persist to localStorage
- Sync across tabs
- Optimistic updates
```
**Output yang Diharapkan:**
- `app/stores/cart.ts` Pinia store
- Composable `useCart` untuk easy access
- LocalStorage persistence
- BroadcastChannel untuk sync
- Type-safe actions
**Agent akan:**
1. ✅ Load skill `frontend-patterns` untuk state management
2. ✅ Create Pinia store dengan TypeScript
3. ✅ Implement persistence plugin
4. ✅ Add optimistic updates
5. ✅ Create comprehensive tests
**Code Example:**
```typescript
// app/stores/cart.ts
import { defineStore } from 'pinia'
interface CartItem {
id: string
name: string
price: number
quantity: number
}
export const useCartStore = defineStore('cart', () => {
const items = ref<CartItem[]>([])
const total = computed(() =>
items.value.reduce((sum, item) => sum + item.price * item.quantity, 0)
)
const addItem = (product: Omit<CartItem, 'quantity'>) => {
const existing = items.value.find((i) => i.id === product.id)
if (existing) {
existing.quantity++
} else {
items.value.push({ ...product, quantity: 1 })
}
}
const removeItem = (id: string) => {
items.value = items.value.filter((i) => i.id !== id)
}
const updateQuantity = (id: string, quantity: number) => {
const item = items.value.find((i) => i.id === id)
if (item) {
item.quantity = Math.max(0, quantity)
if (item.quantity === 0) {
removeItem(id)
}
}
}
// Persist to localStorage
watch(
items,
(newItems) => {
localStorage.setItem('cart', JSON.stringify(newItems))
},
{ deep: true }
)
// Sync across tabs
if (process.client) {
const channel = new BroadcastChannel('cart-sync')
channel.onmessage = (event) => {
items.value = event.data
}
watch(items, (newItems) => {
channel.postMessage(newItems)
})
}
return {
items,
total,
addItem,
removeItem,
updateQuantity,
}
})
// app/composables/useCart.ts
export const useCart = () => useCartStore()
```
---
## 6. Performance Optimization
### Skenario: Optimize Slow Page
**Prompt:**
```
@frontend Page /products sangat lambat. Tolong optimize:
- Initial load time
- Image loading
- Component rendering
- Bundle size
- Lighthouse score target: 90+
```
**Agent akan:**
1. ✅ Load skill `frontend-patterns`
2. ✅ Analyze bundle dengan `nuxi analyze`
3. ✅ Implement lazy loading
4. ✅ Add image optimization
5. ✅ Use virtual scrolling untuk long lists
6. ✅ Run Lighthouse audit
**Optimizations Applied:**
```vue
<!-- Before -->
<script setup lang="ts">
import ProductCard from '~/components/ProductCard.vue'
import HeavyChart from '~/components/HeavyChart.vue'
const { data: products } = await useFetch('/api/products')
</script>
<template>
<div v-for="product in products" :key="product.id">
<img :src="product.image" />
<ProductCard :product="product" />
</div>
<HeavyChart :data="chartData" />
</template>
<!-- After -->
<script setup lang="ts">
// Lazy load heavy component
const HeavyChart = defineAsyncComponent(() => import('~/components/HeavyChart.vue'))
// Fetch with caching
const { data: products } = await useFetch('/api/products', {
key: 'products-list',
getCachedData: (key) => useNuxtData(key).data,
})
</script>
<template>
<!-- Virtual scrolling untuk list panjang -->
<UVirtualScroll :items="products" :item-height="200">
<template #default="{ item: product }">
<!-- Optimized image dengan Nuxt Image -->
<NuxtImg :src="product.image" width="400" height="300" loading="lazy" format="webp" />
<ProductCard :product="product" />
</template>
</UVirtualScroll>
<!-- Lazy load chart -->
<ClientOnly>
<HeavyChart v-if="showChart" :data="chartData" />
</ClientOnly>
</template>
```
**Results:**
- ✅ Bundle size reduced: 500KB → 200KB
- ✅ First Contentful Paint: 3.5s → 1.2s
- ✅ Lighthouse Performance: 65 → 94
- ✅ Images optimized with WebP format
- ✅ Code splitting implemented
---
## 7. Accessibility Review
### Skenario: Audit Existing Component
**Prompt:**
```
@frontend Review accessibility untuk component app/components/Modal.vue
Target: WCAG 2.1 Level AA compliance
```
**Agent akan:**
1. ✅ Load skill `web-design-guidelines`
2. ✅ Check semantic HTML
3. ✅ Verify ARIA attributes
4. ✅ Test keyboard navigation
5. ✅ Check color contrast
6. ✅ Provide detailed report
**Audit Report Example:**
````markdown
# Accessibility Audit: Modal.vue
## Issues Found (6)
### Critical
1. ❌ Missing `role="dialog"` on modal container
2. ❌ No focus trap - keyboard navigation escapes modal
3. ❌ Missing `aria-labelledby` and `aria-describedby`
### Warning
4. ⚠️ Close button lacks accessible label
5. ⚠️ Background color contrast 3.2:1 (needs 4.5:1)
### Info
6. ℹ️ Consider adding focus restoration on close
## Recommended Fixes
### 1. Add proper ARIA attributes
```vue
<template>
<div role="dialog" aria-modal="true" aria-labelledby="modal-title" aria-describedby="modal-desc">
<h2 id="modal-title">{{ title }}</h2>
<p id="modal-desc">{{ description }}</p>
</div>
</template>
```
````
### 2. Implement focus trap
```typescript
import { useFocusTrap } from '@vueuse/integrations/useFocusTrap'
const modalRef = ref<HTMLElement>()
const { activate, deactivate } = useFocusTrap(modalRef)
watch(isOpen, (open) => {
if (open) activate()
else deactivate()
})
```
### 3. Add accessible close button
```vue
<UButton icon="i-heroicons-x-mark" aria-label="Close modal" @click="close" />
```
## After Fixes
✅ All WCAG 2.1 Level AA criteria met
✅ Keyboard navigation: Tab, Shift+Tab, Escape
✅ Screen reader friendly
✅ Color contrast: 7.8:1
```
---
## 8. Bug Fixing
### Skenario: Fix Hydration Mismatch
**Prompt:**
```
@frontend Ada error hydration mismatch di homepage:
"Hydration completed but contains mismatches."
Error terjadi di section hero dengan dynamic timestamp.
````
**Agent akan:**
1. ✅ Load skill `frontend-patterns`
2. ✅ Analyze SSR/CSR differences
3. ✅ Identify root cause
4. ✅ Provide fix dengan explanation
5. ✅ Add preventive measures
**Diagnosis:**
```markdown
# Hydration Mismatch Analysis
## Root Cause
Timestamp generated differently on server vs client:
- Server: Uses UTC time
- Client: Uses local timezone
## Code Causing Issue
```vue
<template>
<p>Last updated: {{ new Date().toLocaleString() }}</p>
</template>
````
## Fix
```vue
<script setup lang="ts">
// Option 1: Client-only rendering
const timestamp = ref('')
onMounted(() => {
timestamp.value = new Date().toLocaleString()
})
</script>
<template>
<ClientOnly fallback="Loading...">
<p>Last updated: {{ timestamp }}</p>
</ClientOnly>
</template>
```
```vue
<!-- Option 2: Use consistent format -->
<script setup lang="ts">
const timestamp = new Date().toISOString()
</script>
<template>
<p>Last updated: {{ timestamp }}</p>
</template>
```
## Prevention
- ✅ Always use ClientOnly for browser APIs
- ✅ Use ISO format untuk dates
- ✅ Test with SSR enabled
- ✅ Add hydration tests
```
---
## Tips untuk Prompt yang Efektif
### ✅ Good Prompts
```
@frontend Build login form dengan email/password, validation, dan error handling
```
- Specific dan actionable
- Jelas output yang diharapkan
- Focused scope
```
@frontend Check accessibility untuk ProductCard component, target WCAG 2.1 AA
```
- Clear success criteria
- Specific component
- Defined standard
### ❌ Bad Prompts
```
@frontend Bantu saya
```
- Terlalu vague
- Tidak ada context
```
@frontend Build full e-commerce website
```
- Scope terlalu luas
- Perlu breakdown
### Best Practices
1. **Be Specific**: Sebutkan exact component/page/feature
2. **Provide Context**: Jika ada existing code, mention file path
3. **Set Standards**: Sebutkan requirements (accessibility, performance, etc.)
4. **Ask Questions**: Agent bisa tanya jika butuh clarification
5. **Iterate**: Start small, kemudian expand
---
## MCP Integration Examples
### Query Nuxt Documentation
```
@frontend Bagaimana cara implement custom error page di Nuxt 4?
```
Agent akan:
1. Query Nuxt MCP untuk error handling docs
2. Get latest Nuxt 4 patterns
3. Provide code example dengan explanation
### Check Nuxt UI Components
```
@frontend Apa komponen terbaik untuk notification system?
```
Agent akan:
1. Query Nuxt UI MCP untuk Toast/Notification components
2. Compare options (`<UNotification>` vs `<UToast>`)
3. Provide implementation recommendation
### Use Playwright for Testing
```
@frontend Create E2E test untuk login flow
```
Agent akan:
1. Use Playwright MCP untuk browser automation
2. Generate test script
3. Add assertions dan error cases
4. Setup test runners
---
## Common Workflows Cheatsheet
| Task | Prompt Example |
|------|----------------|
| Create Component | `@frontend Create Button component dengan variants primary/secondary` |
| Fix Bug | `@frontend Fix hydration error di app/pages/index.vue` |
| Add Feature | `@frontend Add dark mode toggle menggunakan Nuxt UI` |
| Optimize | `@frontend Optimize bundle size untuk /dashboard route` |
| Review | `@frontend Review accessibility untuk Modal component` |
| Test | `@frontend Add unit tests untuk useCart composable` |
| Document | `@frontend Add JSDoc comments untuk ProductCard props` |
| Refactor | `@frontend Refactor UserList component, extract reusable logic` |
---
## Troubleshooting
### Agent tidak load skill yang expected
**Solusi:**
- Explicitly mention skill: `@frontend Gunakan skill web-design-guidelines untuk review ini`
- Check skill available: List skills di `.opencode/skills/` dan `~/.opencode/skills/`
### MCP server tidak respond
**Solusi:**
- Check `.opencode/config.json` - pastikan `enabled: true`
- Restart OpenCode session
- Verify internet connection (untuk remote MCP)
### Agent terlalu verbose
**Solusi:**
- Add constraint: `@frontend (concise) Create button component`
- Request specific output: `@frontend Show only code, no explanation`
### Need Figma access
**Solusi:**
1. Get token dari https://www.figma.com/developers/api#access-tokens
2. Set `FIGMA_ACCESS_TOKEN` di `.env`
3. Enable di `.opencode/config.json`: `"enabled": true`
4. Restart session
---
## Next Steps
Setelah familiar dengan examples di atas:
1. **Explore Skills**: Check `~/.opencode/skills/` untuk available skills
2. **Read Workflows**: Baca `WORKFLOWS.md` untuk detailed workflows
3. **Check MCP Guide**: Read `MCP_GUIDE.md` untuk advanced MCP usage
4. **Practice**: Start dengan simple components, gradually increase complexity
5. **Contribute**: Share your own examples dan workflows dengan team
---
**Happy Coding! 🚀**
Untuk pertanyaan atau feedback:
- Check dokumentasi lengkap di `.opencode/agents/`
- Atau mention `@frontend` dengan pertanyaan Anda
```