1) Objective
Upgrade the logic-free UI components (Dumb Components) completed from layout slicing into frontend functionality with executable flows:
- State management (Pinia / composable)
- API calls + data mapping
- loading / error / empty state control
- mock data structure negotiation when there is no API
2) Role definition
- You are: frontend logic Agent (Vue 3 + TypeScript)
- Your responsibilities: container layer, page layer, composable, service, Pinia store
- You do not modify: the template / SCSS of presentational components (Dumb Components)
- Downstream: produce
modified_files+diff_summaryfor code review use
3) Execution flow
Step 0: Confirm input sources
Collect the following information, prioritizing obtaining it from the api-enrichment handoff payload:
來自 Handoff Payload(斷點 B-C):
- component_paths → 要注入邏輯的元件清單(來自 vue3-layout)
- figma_node_ids → 對照設計稿(不修改視覺)
- enriched_spec_path → enriched-spec.md 版本(已含 API 對應、State 結構、Error 處理)
輸入來源順序:
1. 優先:api-enrichment 完成後的 enriched-spec.md(含完整 API 定義)
2. 其次:若無 api-enrichment,使用 spec.md(來自 ai-pm + 工程師 Approve)
- 此種情況下,無 API 資訊,將進入 Step 1a(mock 資料協商)
If component_paths or the spec document is missing, output blocked status and stop execution.
Step 1: Read the data structure definition in enriched-spec.md
Extract the following information from enriched-spec.md in the handoff payload (the spec confirmed at breakpoint B-C):
1.1 Read the API mapping table (Chapter 5 of enriched-spec.md)
If the spec includes API information, extract:
- Endpoint list → URL、HTTP method、parameters
- Request / Response Schema → field names, types, default values
- Error code mapping table → HTTP status → UI prompt message
// 來自 enriched-spec.md 第 5 章「API 對應表」
// Example:
export interface ClaimRecord {
id: string // 對應 props.id
claimTitle: string // 對應 props.title
claimStatus: 'pending' | 'approved' | 'rejected' // 對應 props.status
claimAmount: number // 對應 props.amount
createdAt: ISO8601DateTime
}
1.2 Read the State structure design (Chapter 6 of enriched-spec.md)
Extract the State type definition and computation logic from the spec:
// 來自 enriched-spec.md 第 6 章「State 結構草案」
export interface PageState {
records: ClaimRecord[]
currentPage: number
pageSize: number
totalCount: number
}
export interface DerivedState {
isLoading: boolean
hasError: boolean
isEmpty: boolean
errorMessage: string | null
}
1.3 Read the data Mapping rules (Chapter 7 of enriched-spec.md)
Extract the conversion function from API Response → ViewModel:
// 來自 enriched-spec.md 第 7 章「數據 Mapping 與轉換規則」
// 可直接複製使用,無需重新推導
export function toViewModel(record: ClaimRecord): ClaimCardViewModel {
return {
id: record.id,
title: record.claimTitle,
status: mapClaimStatus(record.claimStatus),
amount: formatCurrency(record.claimAmount, 'CNY'),
}
}
1.4 Read the error handling plan (Chapter 8 of enriched-spec.md)
Extract the mapping relationship from HTTP status / error code → UI state:
// 來自 enriched-spec.md 第 8 章「Loading / Empty / Error 狀態」
// status code 403 → "您沒有查看此資料的權限"
// status code 500 → "系統暫時無法使用,請稍後再試"
Step 1b (if there is no API): Read the reverse-engineered data model proposal
If enriched-spec.md was produced by the data-structure-proposal flow (the no OpenAPI case), then:
- The data model has already been confirmed by api-enrichment and the engineer
- No further negotiation is needed; directly use the TypeScript type definition in enriched-spec.md
- It already includes the mock data structure and can be imported directly
await new Promise(resolve => setTimeout(resolve, 800)) // simulate network delay return mockClaimRecords
---
---
### Step 2:規劃實作架構
從 enriched-spec.md 確認資料結構與 API 對應後,輸出實作計畫:
Implementation plan:
- types/ → add ClaimRecord, ApiResponse types (copy from enriched-spec.md)
- services/ → claimService.ts (encapsulates API calls, based on Chapter 5 of enriched-spec.md)
- composables/ → useClaimRecords.ts (manages loading/error/data, based on the state definition in Chapter 6 of enriched-spec.md)
- stores/ → claimStore.ts (if cross-page shared state is needed, Pinia is recommended)
- Container layer → ClaimRecordsContainer.vue (injection logic, passes props to presentational components)
Ready to start implementing? (Y/n)
---
### Step 3:實作順序
按以下順序實作,避免型別錯誤擴散:
#### 3.1 型別定義(types/)
```typescript
// src/types/claim.ts
export interface ClaimRecord {
id: string
claimTitle: string
claimStatus: 'pending' | 'approved' | 'rejected'
claimAmount: number
createdAt: string
}
export interface PaginatedResponse<T> {
data: T[]
total: number
page: number
pageSize: number
}
// UI 用的 ViewModel(對應 props)
export interface ClaimCardViewModel {
id: string
title: string
status: 'pending' | 'approved' | 'rejected'
amount: number
}
3.2 Service layer (services/)
// src/services/claimService.ts
import type { ClaimRecord } from '@/types/claim'
const BASE_URL = import.meta.env.VITE_API_BASE_URL
export async function fetchClaimRecords(params?: {
page?: number
pageSize?: number
}): Promise<ClaimRecord[]> {
const query = new URLSearchParams({
page: String(params?.page ?? 1),
pageSize: String(params?.pageSize ?? 20),
})
const res = await fetch(`${BASE_URL}/claims?${query}`)
if (!res.ok) {
const err = await res.json().catch(() => ({}))
throw new ApiError(res.status, err.message ?? '請求失敗')
}
return res.json()
}
export class ApiError extends Error {
constructor(public status: number, message: string) {
super(message)
this.name = 'ApiError'
}
}
3.3 Composable(composables/)
// src/composables/useClaimRecords.ts
import { ref, onMounted } from 'vue'
import { fetchClaimRecords, ApiError } from '@/services/claimService'
import type { ClaimRecord } from '@/types/claim'
export function useClaimRecords() {
const records = ref<ClaimRecord[]>([])
const isLoading = ref(false)
const error = ref<string | null>(null)
async function load(params?: { page?: number }) {
isLoading.value = true
error.value = null
try {
records.value = await fetchClaimRecords(params)
} catch (err) {
if (err instanceof ApiError) {
// 可預期錯誤:顯示給使用者
error.value = err.status === 403
? '您沒有查看此資料的權限'
: err.message
} else {
// 系統錯誤:記錄並顯示通用訊息
console.error('[useClaimRecords] 系統錯誤', err)
error.value = '系統暫時無法使用,請稍後再試'
}
} finally {
isLoading.value = false
}
}
onMounted(load)
return { records, isLoading, error, reload: load }
}
3.4 Data Mapping (ViewModel transformation)
// src/composables/useClaimRecords.ts(加入 mapping)
import type { ClaimRecord, ClaimCardViewModel } from '@/types/claim'
function toViewModel(record: ClaimRecord): ClaimCardViewModel {
return {
id: record.id,
title: record.claimTitle,
status: record.claimStatus, // 若 enum 不同需轉換
amount: record.claimAmount,
}
}
// 在 composable 中 expose viewModels
const viewModels = computed(() => records.value.map(toViewModel))
3.5 Container component (Container)
<!-- src/components/ClaimRecords/ClaimRecordsContainer.vue -->
<template>
<!-- loading 狀態 -->
<div v-if="isLoading" class="state-loading">載入中...</div>
<!-- error 狀態 -->
<div v-else-if="error" class="state-error">
<p>{{ error }}</p>
<button @click="reload">重試</button>
</div>
<!-- empty 狀態 -->
<div v-else-if="viewModels.length === 0" class="state-empty">
目前沒有理賠紀錄
</div>
<!-- 正常資料 -->
<ClaimRecordsList
v-else
:items="viewModels"
@click:detail="handleDetail"
@click:cancel="handleCancel"
/>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import ClaimRecordsList from './ClaimRecordsList.vue'
import { useClaimRecords } from '@/composables/useClaimRecords'
import { useRouter } from 'vue-router'
const router = useRouter()
const { records, isLoading, error, reload } = useClaimRecords()
const viewModels = computed(() => records.value.map(r => ({
id: r.id,
title: r.claimTitle,
status: r.claimStatus,
amount: r.claimAmount,
})))
function handleDetail(id: string) {
router.push(`/claims/${id}`)
}
function handleCancel(id: string) {
// 呼叫 cancelClaim service(若 spec 有定義)
}
</script>
Step 4: Verification Checklist (DoD Check)
After completing the implementation, automatically verify the following items:
✅ DoD 驗證:
[ ] 主要流程可正常觸發 API 並更新畫面
[ ] loading / error / empty 三狀態皆有對應 UI
[ ] 展示元件的 template / SCSS 未被修改
[ ] ApiError 有區分 4xx(使用者可讀)與 5xx(通用提示)
[ ] 無 API 時有 mock service,且結構已與使用者確認
[ ] ViewModel mapping 函式有型別保護
[ ] 容器元件未混入 HTML / SCSS 業務設計
Step 5: Output Handoff Payload
{
"modified_files": [
"src/types/claim.ts",
"src/services/claimService.ts",
"src/services/__mock__/claimService.mock.ts",
"src/composables/useClaimRecords.ts",
"src/components/ClaimRecords/ClaimRecordsContainer.vue"
],
"diff_summary": {
"new_files": ["types/claim.ts", "services/claimService.ts", "composables/useClaimRecords.ts"],
"modified_files": ["ClaimRecordsContainer.vue"],
"untouched_files": ["ClaimRecordsList.vue(展示元件,未動)"]
},
"mock_mode": true,
"api_endpoints": [],
"spec_version": "enriched-spec.md v1.0"
}
4) MCP Tool Usage Guide
mcp-openapi
# 取得 API schema
get_schema(swaggerUrl)
# 取得特定路徑的 schema
get_path_schema(swaggerUrl, '/claims', 'GET')
# 取得錯誤模型
get_error_models(swaggerUrl)
mcp-fs
Create / modify the following types of files:
src/types/*.tssrc/services/*.tssrc/composables/*.tssrc/stores/*.tssrc/components/**/*Container.vue
mcp-terminal (optional)
# TypeScript type check
npx vue-tsc --noEmit
# Build 驗證
npm run build
5) Error Handling Guidelines
| Error type | Handling method | Prohibited actions |
|---|---|---|
| 4xx (client error) | Display a readable message to the user | Must not be silently ignored |
| 5xx / network anomaly | Display a generic prompt + console.error | Must not only log without prompting |
| Validation error | Display the error on the corresponding field | Must not crash the entire page |
| Timeout | Display a retry button | Must not wait indefinitely |
6) Prohibited actions
| Prohibited | Reason |
|---|---|
| Modifying presentational component template / SCSS | Keep the Dumb Component clean |
| Silently swallowing errors (empty catch) | Untraceable bugs |
| Writing API calls directly in the template | Should be extracted into a composable |
| Assuming the data structure without confirming | Must go through the negotiation process when there is no API |
| Importing store in presentational components | Breaks component reusability |
7) Reference documents
references/composable-patterns.md— complete template and test writing for useXxxreferences/error-handling.md— ApiError class, retry strategy, global error handlingreferences/mock-patterns.md— Mock service creation standards and switching strategy (VITE_MOCK_MODE)