Frontend-Implementation AgentSkill
Overview
frontend-implementation is the seventh and final step in the SSDAM execution pipeline. It reads the frontend-design.TSK-NNN.md specification and directly implements all frontend code into the project root (Svelte components, stores, API client, routes, tests).
This skill is designed for autonomous execution by Cursor AI agents — it requires no human intervention and produces working, tested code.
Skill Chain Context
frontend-design.TSK-NNN.md (required)
+ backend-design.TSK-NNN.md (optional)
↓
[frontend-implementation] ← YOU ARE HERE
↓
Code in project_root/:
src/routes/ ← SvelteKit pages
src/lib/
components/ ← Svelte components
stores/ ← Svelte stores
api/ ← API client functions
types/ ← TypeScript interfaces
tests/ ← Component and store tests
Trigger and I/O
| Aspect |
Details |
| Trigger |
/frontend-implementation <task-spec-path> |
| Input |
task-spec.TSK-NNN.yaml + frontend-design.TSK-NNN.md (required) + backend-design.TSK-NNN.md (optional) |
| Output |
Code written directly to project_root/ (no design document) |
| Framework |
Svelte 5 (or from task-spec.tech_stack.frontend) |
Execution Procedure
Step 1: Load Inputs and Create Implementation Plan
Parse task-spec.TSK-NNN.yaml:
- Extract
execution_plan.tech_stack.frontend (framework: Svelte 5 by default)
- Extract
execution_plan.tech_stack.project_root (where to write code)
- Extract
acceptance_criteria (requirements for the implementation)
- Extract
requirement_ids (for traceability)
Read frontend-design.TSK-NNN.md fully:
- Extract
pages list (routes and page components)
- Extract
components list (all components to implement)
- Extract
stores list (Svelte stores to create)
- Extract
api_integration (API client functions)
- Extract
file_structure (directory/file plan)
- Extract
test_strategy (what to test)
If backend-design.TSK-NNN.md exists:
- Read
api_endpoints for precise URL, method, schema details
- Read
schemas for exact TypeScript interface definitions
- Use these for API client implementation (override frontend-design if more precise)
Create ordered implementation plan:
Phase 1: TypeScript types
└─ Create src/lib/types/{domain}.ts files with all interfaces
Phase 2: Svelte stores
└─ Create src/lib/stores/{storeName}.ts files
Phase 3: API client
└─ Create src/lib/api/{domain}.ts files with fetch functions
Phase 4: Atomic components
└─ Create src/lib/components/atomic/{Component}.svelte (bottom-up)
Phase 5: Feature components
└─ Create src/lib/components/features/{Feature}/{Component}.svelte
Phase 6: Layout components
└─ Create src/lib/components/layout/{Layout}.svelte
Phase 7: Page components
└─ Create src/routes/{route}/+page.svelte
Phase 8: Tests
└─ Create tests/ directory structure and test files
Phase 9: Verification
└─ Run npm run test, verify all tests pass
└─ Verify acceptance_criteria are met
Rationale for bottom-up order: Atomic components don't depend on others; feature components depend on atomic; page components depend on features. This ensures dependencies are met as we build.
Step 2: Implement TypeScript Interfaces
For each type mentioned in frontend-design.api_integration and frontend-design.stores:
Identify all types needed:
- Response types (from API endpoints)
- Request types (for POST/PUT bodies)
- Store state interfaces
- Component-specific interfaces (if needed)
Create type files in src/lib/types/:
- Group related types by domain (e.g.,
src/lib/types/media.ts, src/lib/types/auth.ts)
- If backend-design exists, copy exact field names and types from its schemas
Example implementation:
// src/lib/types/media.ts
export interface MediaFile {
id: string;
filename: string;
size: number;
mimetype: string;
uploadedAt: Date;
ownerId: string;
}
export interface UploadFileRequest {
filename: string;
mimetype: string;
size: number;
}
export interface MediaFilesState {
items: MediaFile[];
loading: boolean;
error: string | null;
selectedIds: string[];
}
Ensure all types are TypeScript interfaces, not any.
Export all types from a barrel file (optional but recommended):
// src/lib/types/index.ts
export * from './media';
export * from './auth';
Step 3: Implement Svelte Stores
For each store in frontend-design.stores:
Create store file at src/lib/stores/{storeName}.ts:
Implement writable store with initial state:
import { writable, derived } from 'svelte/store';
import type { MediaFilesState, MediaFile } from '../types';
const initialState: MediaFilesState = {
items: [],
loading: false,
error: null,
selectedIds: []
};
export const mediaFilesStore = writable<MediaFilesState>(initialState);
Implement all actions from frontend-design:
export async function fetchFiles() {
mediaFilesStore.update(state => ({ ...state, loading: true, error: null }));
try {
const files = await api.fetchMediaFiles();
mediaFilesStore.update(state => ({ ...state, items: files, loading: false }));
} catch (err) {
mediaFilesStore.update(state => ({
...state,
error: err instanceof Error ? err.message : 'Unknown error',
loading: false
}));
}
}
export function selectFile(id: string) {
mediaFilesStore.update(state => ({
...state,
selectedIds: [...state.selectedIds, id]
}));
}
export function deselectFile(id: string) {
mediaFilesStore.update(state => ({
...state,
selectedIds: state.selectedIds.filter(sid => sid !== id)
}));
}
Implement derived stores (if defined in frontend-design):
export const selectedCount$ = derived(mediaFilesStore, $store => $store.selectedIds.length);
Export all store functions and derived stores.
Key points:
- Do NOT call API directly in stores; stores call API client functions (defined in Step 4)
- Always set
loading: true before async operation, false in finally
- Always catch errors and update
error field
- Never use
any type; use typed state interface
Step 4: Implement API Client
For each endpoint in frontend-design.api_integration:
Create or update src/lib/api/{domain}.ts:
Import types and utility functions:
import type { MediaFile, UploadFileRequest } from '../types';
Implement async fetch functions:
const API_BASE = process.env.VITE_API_BASE || 'http://localhost:3000';
export async function fetchMediaFiles(): Promise<MediaFile[]> {
const res = await fetch(`${API_BASE}/api/media/files`, {
method: 'GET',
headers: { 'Content-Type': 'application/json' }
});
if (!res.ok) throw new Error(`Failed to fetch files: ${res.statusText}`);
return res.json();
}
export async function uploadFile(file: File): Promise<MediaFile> {
const formData = new FormData();
formData.append('file', file);
const res = await fetch(`${API_BASE}/api/uploads`, {
method: 'POST',
body: formData
});
if (!res.ok) throw new Error(`Upload failed: ${res.statusText}`);
return res.json();
}
export async function deleteFile(id: string): Promise<void> {
const res = await fetch(`${API_BASE}/api/media/files/${id}`, {
method: 'DELETE',
headers: { 'Content-Type': 'application/json' }
});
if (!res.ok) throw new Error(`Delete failed: ${res.statusText}`);
}
Add Authorization header for auth_required endpoints:
import { get } from 'svelte/store';
import { authStore } from '../stores';
export async function fetchPrivateFiles(): Promise<MediaFile[]> {
const $auth = get(authStore);
const res = await fetch(`${API_BASE}/api/media/files`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${$auth.token}`
}
});
if (!res.ok) throw new Error(`Failed to fetch: ${res.statusText}`);
return res.json();
}
Error handling:
- Always throw descriptive Error objects (not raw strings)
- Include HTTP status or message
- Do NOT catch and suppress errors — let caller handle them
Do NOT make HTTP calls directly in stores or components — always use API client.
Step 5: Implement Atomic Components
Implement reusable, presentation-only components in src/lib/components/atomic/:
For each atomic component in frontend-design:
Create .svelte file with TypeScript:
<script lang="ts">
interface Props {
label: string;
type?: 'button' | 'submit' | 'reset';
disabled?: boolean;
size?: 'sm' | 'md' | 'lg';
variant?: 'primary' | 'secondary' | 'danger';
}
let { label, type = 'button', disabled = false, size = 'md', variant = 'primary' }: Props = $props();
</script>
<button
{type}
{disabled}
class:disabled
class:sm={size === 'sm'}
class:md={size === 'md'}
class:lg={size === 'lg'}
class:primary={variant === 'primary'}
class:secondary={variant === 'secondary'}
class:danger={variant === 'danger'}
on:click
>
{label}
</button>
<style module>
button {
@apply px-4 py-2 rounded font-medium transition;
}
.primary { @apply bg-blue-600 text-white hover:bg-blue-700; }
.secondary { @apply bg-gray-200 text-gray-800 hover:bg-gray-300; }
.danger { @apply bg-red-600 text-white hover:bg-red-700; }
.sm { @apply text-sm px-2 py-1; }
.lg { @apply text-lg px-6 py-3; }
.disabled { @apply opacity-50 cursor-not-allowed; }
</style>
Key rules:
- All props must be typed (no
any)
- No API calls, no store mutations
- Use TailwindCSS for styling
- Emit events if needed:
on:click, custom events via dispatch()
- Props should be simple types or interfaces, not functions
Implement all atomic components before proceeding to feature components.
Step 6: Implement Feature and Page Components
Implement feature components in src/lib/components/features/:
Feature components (medium-complexity, domain-specific):
<script lang="ts">
import { onMount } from 'svelte';
import { mediaFilesStore } from '../../stores/mediaFiles';
import * as api from '../../api/mediaFiles';
import Button from '../atomic/Button.svelte';
import MediaGrid from './MediaGallery/Grid.svelte';
let loading = false;
let error: string | null = null;
async function handleUpload() {
loading = true;
error = null;
try {
const input = document.createElement('input');
input.type = 'file';
input.onchange = async (e) => {
const file = (e.target as HTMLInputElement).files?.[0];
if (file) {
const result = await api.uploadFile(file);
mediaFilesStore.addFile(result);
}
};
input.click();
} catch (err) {
error = err instanceof Error ? err.message : 'Upload failed';
} finally {
loading = false;
}
}
onMount(async () => {
try {
await mediaFilesStore.fetchFiles();
} catch (err) {
error = err instanceof Error ? err.message : 'Failed to load files';
}
});
</script>
<div class="space-y-4">
{#if error}
<div class="p-4 bg-red-100 text-red-800 rounded">
{error}
</div>
{/if}
<Button label="Upload File" on:click={handleUpload} disabled={loading} />
{#if loading}
<div class="text-center py-8">Loading...</div>
{:else}
<MediaGrid items={$mediaFilesStore.items} />
{/if}
</div>
Page components in src/routes/{route}/+page.svelte:
<script lang="ts">
import AppLayout from '../../lib/components/layout/AppLayout.svelte';
import MediaUpload from '../../lib/components/features/MediaUpload.svelte';
</script>
<AppLayout>
<div class="container mx-auto">
<h1 class="text-2xl font-bold mb-6">Media Library</h1>
<MediaUpload />
</div>
</AppLayout>
Key principles:
- Feature components call API functions and update stores
- Page components orchestrate layout and feature components
- All async operations must have loading + error states
- Use
onMount to load page data
- Use
$store syntax to read store values reactively
Step 7: Implement Loading and Error States
For every API call in every component:
Create reactive state variables:
let loading = false;
let error: string | null = null;
Wrap API calls with try-catch-finally:
async function fetchData() {
loading = true;
error = null;
try {
const data = await api.fetchData();
store.update(s => ({ ...s, data }));
} catch (err) {
error = err instanceof Error ? err.message : 'An error occurred';
} finally {
loading = false;
}
}
Show loading UI while loading:
{#if loading}
<Skeleton />
{:else if error}
<ErrorMessage message={error} />
{:else}
<Content />
{/if}
Disable form submissions while loading:
<Button
label="Submit"
on:click={handleSubmit}
disabled={loading}
/>
Step 8: Write Tests and Verify
Create test files using Vitest + Svelte Testing Library:
// tests/components/Button.test.ts
import { describe, it, expect } from 'vitest';
import { render, screen } from '@testing-library/svelte';
import userEvent from '@testing-library/user-event';
import Button from '../../src/lib/components/atomic/Button.svelte';
describe('Button', () => {
it('renders with label', () => {
render(Button, { props: { label: 'Click me' } });
expect(screen.getByText('Click me')).toBeInTheDocument();
});
it('dispatches click event', async () => {
const { component } = render(Button, { props: { label: 'Click' } });
let clicked = false;
component.$on('click', () => { clicked = true; });
await userEvent.click(screen.getByRole('button'));
expect(clicked).toBe(true);
});
it('disables when disabled prop is true', () => {
render(Button, { props: { label: 'Click', disabled: true } });
expect(screen.getByRole('button')).toBeDisabled();
});
});
Test store actions:
// tests/stores/mediaFiles.test.ts
import { describe, it, expect } from 'vitest';
import { mediaFilesStore, selectFile } from '../../src/lib/stores/mediaFiles';
describe('mediaFilesStore', () => {
it('initializes with empty items', () => {
let state;
mediaFilesStore.subscribe(s => { state = s; });
expect(state.items).toEqual([]);
});
it('selectFile adds ID to selectedIds', () => {
selectFile('file-123');
let state;
mediaFilesStore.subscribe(s => { state = s; });
expect(state.selectedIds).toContain('file-123');
});
});
Run tests and verify they pass:
npm run test
Verify acceptance criteria:
- For each criterion in task-spec, verify the implementation satisfies it
- Manual testing: navigate the app, test key workflows
- Check for console errors or warnings
Step 9: Final Verification
Run full test suite:
npm run test
- All tests must pass.
- No errors, no warnings.
Verify file structure matches frontend-design.file_structure:
- All specified directories exist
- All specified files exist
- No unexpected files
Verify acceptance criteria are met:
- For each criterion in task-spec.acceptance_criteria, verify the implementation satisfies it
- Document any unmet criteria (should be none)
Check for TypeScript errors:
npm run check
- No TypeScript errors or warnings
Check for build errors:
npm run build
- Build succeeds without errors
Post-Execution
On successful completion:
✓ Frontend implementation complete for TSK-NNN.
Files created/modified:
✓ src/lib/types/media.ts (3 interfaces)
✓ src/lib/types/auth.ts (2 interfaces)
✓ src/lib/stores/mediaFiles.ts
✓ src/lib/stores/auth.ts
✓ src/lib/api/mediaFiles.ts (5 functions)
✓ src/lib/api/auth.ts (3 functions)
✓ src/lib/components/atomic/Button.svelte
✓ src/lib/components/atomic/TextField.svelte
✓ src/lib/components/layout/AppLayout.svelte
✓ src/lib/components/features/MediaGallery/Grid.svelte
✓ src/routes/+page.svelte
✓ src/routes/library/+page.svelte
✓ tests/ (N test files)
Tests:
npm run test result: 24 passed, 0 failed
Build:
npm run build: SUCCESS
Acceptance criteria verified:
✓ Users can upload media files
✓ Users can browse media gallery
✓ Users can delete media files
✓ All API calls include auth headers
✓ All async operations show loading/error states
Next: Deploy to production or proceed to next task phase
Error Handling
| Error |
Action |
frontend-design.TSK-NNN.md not found |
Stop. Instruct agent: "Run /frontend-design first." |
| TypeScript compilation errors |
Stop and report: "[list errors]". Agent must fix. |
| Test failures |
Stop and report: "[list failed tests]". Agent must fix. |
| Missing prop typing |
Stop and report: "[component] has untyped props". Agent must fix. |
| API call without auth header |
Stop and report: "[function] missing auth header". Agent must fix. |
| Inline API calls in components |
Stop and report: "[component] makes fetch directly". Refactor to API client. |
References
- input.template.yaml: Schema for frontend-design input, task-spec, backend-design
- output.template.yaml: Contract showing what was implemented
- rules.md: Mandatory conventions (must enforce during implementation)
Compatibility Notes
- Framework: Read
task-spec.execution_plan.tech_stack.frontend to determine framework. This skill is optimized for Svelte 5 but patterns apply to other frameworks (React, Vue, etc.).
- Project setup: Assumes SvelteKit (v2+) with TypeScript, Tailwind CSS, Vitest pre-configured.
- API base: Reads
VITE_API_BASE environment variable; defaults to http://localhost:3000.
- Auth: Assumes JWT token in
authStore.token; adjust for other auth methods.
1---2name: frontend-implementation3description: Final frontend step in the SSDAM execution chain. Reads frontend-design and directly implements frontend code (Svelte components, stores, API client, routes, tests) in the project root. This skill is for Cursor AI agent autonomous execution.4---56# Frontend-Implementation AgentSkill78## Overview910**frontend-implementation** is the seventh and final step in the SSDAM execution pipeline. It reads the `frontend-design.TSK-NNN.md` specification and directly implements all frontend code into the project root (Svelte components, stores, API client, routes, tests).1112This skill is designed for **autonomous execution by Cursor AI agents** — it requires no human intervention and produces working, tested code.1314## Skill Chain Context1516```17frontend-design.TSK-NNN.md (required)18 + backend-design.TSK-NNN.md (optional)19 ↓20[frontend-implementation] ← YOU ARE HERE21 ↓22Code in project_root/:23 src/routes/ ← SvelteKit pages24 src/lib/25 components/ ← Svelte components26 stores/ ← Svelte stores27 api/ ← API client functions28 types/ ← TypeScript interfaces29 tests/ ← Component and store tests30```3132## Trigger and I/O3334| Aspect | Details |35|--------|---------|36| **Trigger** | `/frontend-implementation <task-spec-path>` |37| **Input** | `task-spec.TSK-NNN.yaml` + `frontend-design.TSK-NNN.md` (required) + `backend-design.TSK-NNN.md` (optional) |38| **Output** | Code written directly to `project_root/` (no design document) |39| **Framework** | Svelte 5 (or from task-spec.tech_stack.frontend) |4041## Execution Procedure4243### Step 1: Load Inputs and Create Implementation Plan44451. **Parse task-spec.TSK-NNN.yaml:**46 - Extract `execution_plan.tech_stack.frontend` (framework: Svelte 5 by default)47 - Extract `execution_plan.tech_stack.project_root` (where to write code)48 - Extract `acceptance_criteria` (requirements for the implementation)49 - Extract `requirement_ids` (for traceability)50512. **Read frontend-design.TSK-NNN.md fully:**52 - Extract `pages` list (routes and page components)53 - Extract `components` list (all components to implement)54 - Extract `stores` list (Svelte stores to create)55 - Extract `api_integration` (API client functions)56 - Extract `file_structure` (directory/file plan)57 - Extract `test_strategy` (what to test)58593. **If backend-design.TSK-NNN.md exists:**60 - Read `api_endpoints` for precise URL, method, schema details61 - Read `schemas` for exact TypeScript interface definitions62 - Use these for API client implementation (override frontend-design if more precise)63644. **Create ordered implementation plan:**65 ```66 Phase 1: TypeScript types67 └─ Create src/lib/types/{domain}.ts files with all interfaces6869 Phase 2: Svelte stores70 └─ Create src/lib/stores/{storeName}.ts files7172 Phase 3: API client73 └─ Create src/lib/api/{domain}.ts files with fetch functions7475 Phase 4: Atomic components76 └─ Create src/lib/components/atomic/{Component}.svelte (bottom-up)7778 Phase 5: Feature components79 └─ Create src/lib/components/features/{Feature}/{Component}.svelte8081 Phase 6: Layout components82 └─ Create src/lib/components/layout/{Layout}.svelte8384 Phase 7: Page components85 └─ Create src/routes/{route}/+page.svelte8687 Phase 8: Tests88 └─ Create tests/ directory structure and test files8990 Phase 9: Verification91 └─ Run npm run test, verify all tests pass92 └─ Verify acceptance_criteria are met93 ```9495**Rationale for bottom-up order:** Atomic components don't depend on others; feature components depend on atomic; page components depend on features. This ensures dependencies are met as we build.9697---9899### Step 2: Implement TypeScript Interfaces100101For each type mentioned in `frontend-design.api_integration` and `frontend-design.stores`:1021031. **Identify all types needed:**104 - Response types (from API endpoints)105 - Request types (for POST/PUT bodies)106 - Store state interfaces107 - Component-specific interfaces (if needed)1081092. **Create type files in `src/lib/types/`:**110 - Group related types by domain (e.g., `src/lib/types/media.ts`, `src/lib/types/auth.ts`)111 - If backend-design exists, copy exact field names and types from its schemas1121133. **Example implementation:**114 ```typescript115 // src/lib/types/media.ts116 export interface MediaFile {117 id: string;118 filename: string;119 size: number;120 mimetype: string;121 uploadedAt: Date;122 ownerId: string;123 }124125 export interface UploadFileRequest {126 filename: string;127 mimetype: string;128 size: number;129 }130131 export interface MediaFilesState {132 items: MediaFile[];133 loading: boolean;134 error: string | null;135 selectedIds: string[];136 }137 ```1381394. **Ensure all types are TypeScript interfaces, not `any`.**1405. **Export all types from a barrel file (optional but recommended):**141 ```typescript142 // src/lib/types/index.ts143 export * from './media';144 export * from './auth';145 ```146147---148149### Step 3: Implement Svelte Stores150151For each store in `frontend-design.stores`:1521531. **Create store file at `src/lib/stores/{storeName}.ts`:**1541552. **Implement writable store with initial state:**156 ```typescript157 import { writable, derived } from 'svelte/store';158 import type { MediaFilesState, MediaFile } from '../types';159160 const initialState: MediaFilesState = {161 items: [],162 loading: false,163 error: null,164 selectedIds: []165 };166167 export const mediaFilesStore = writable<MediaFilesState>(initialState);168 ```1691703. **Implement all actions from frontend-design:**171 ```typescript172 export async function fetchFiles() {173 mediaFilesStore.update(state => ({ ...state, loading: true, error: null }));174 try {175 const files = await api.fetchMediaFiles();176 mediaFilesStore.update(state => ({ ...state, items: files, loading: false }));177 } catch (err) {178 mediaFilesStore.update(state => ({179 ...state,180 error: err instanceof Error ? err.message : 'Unknown error',181 loading: false182 }));183 }184 }185186 export function selectFile(id: string) {187 mediaFilesStore.update(state => ({188 ...state,189 selectedIds: [...state.selectedIds, id]190 }));191 }192193 export function deselectFile(id: string) {194 mediaFilesStore.update(state => ({195 ...state,196 selectedIds: state.selectedIds.filter(sid => sid !== id)197 }));198 }199 ```2002014. **Implement derived stores (if defined in frontend-design):**202 ```typescript203 export const selectedCount$ = derived(mediaFilesStore, $store => $store.selectedIds.length);204 ```2052065. **Export all store functions and derived stores.**207208**Key points:**209- Do NOT call API directly in stores; stores call API client functions (defined in Step 4)210- Always set `loading: true` before async operation, `false` in finally211- Always catch errors and update `error` field212- Never use `any` type; use typed state interface213214---215216### Step 4: Implement API Client217218For each endpoint in `frontend-design.api_integration`:2192201. **Create or update `src/lib/api/{domain}.ts`:**2212222. **Import types and utility functions:**223 ```typescript224 import type { MediaFile, UploadFileRequest } from '../types';225 ```2262273. **Implement async fetch functions:**228 ```typescript229 const API_BASE = process.env.VITE_API_BASE || 'http://localhost:3000';230231 export async function fetchMediaFiles(): Promise<MediaFile[]> {232 const res = await fetch(`${API_BASE}/api/media/files`, {233 method: 'GET',234 headers: { 'Content-Type': 'application/json' }235 });236237 if (!res.ok) throw new Error(`Failed to fetch files: ${res.statusText}`);238 return res.json();239 }240241 export async function uploadFile(file: File): Promise<MediaFile> {242 const formData = new FormData();243 formData.append('file', file);244245 const res = await fetch(`${API_BASE}/api/uploads`, {246 method: 'POST',247 body: formData248 });249250 if (!res.ok) throw new Error(`Upload failed: ${res.statusText}`);251 return res.json();252 }253254 export async function deleteFile(id: string): Promise<void> {255 const res = await fetch(`${API_BASE}/api/media/files/${id}`, {256 method: 'DELETE',257 headers: { 'Content-Type': 'application/json' }258 });259260 if (!res.ok) throw new Error(`Delete failed: ${res.statusText}`);261 }262 ```2632644. **Add Authorization header for auth_required endpoints:**265 ```typescript266 import { get } from 'svelte/store';267 import { authStore } from '../stores';268269 export async function fetchPrivateFiles(): Promise<MediaFile[]> {270 const $auth = get(authStore);271 const res = await fetch(`${API_BASE}/api/media/files`, {272 method: 'GET',273 headers: {274 'Content-Type': 'application/json',275 'Authorization': `Bearer ${$auth.token}`276 }277 });278279 if (!res.ok) throw new Error(`Failed to fetch: ${res.statusText}`);280 return res.json();281 }282 ```2832845. **Error handling:**285 - Always throw descriptive Error objects (not raw strings)286 - Include HTTP status or message287 - Do NOT catch and suppress errors — let caller handle them2882896. **Do NOT make HTTP calls directly in stores or components — always use API client.**290291---292293### Step 5: Implement Atomic Components294295Implement reusable, presentation-only components in `src/lib/components/atomic/`:2962971. **For each atomic component in frontend-design:**2982992. **Create `.svelte` file with TypeScript:**300 ```svelte301 <script lang="ts">302 interface Props {303 label: string;304 type?: 'button' | 'submit' | 'reset';305 disabled?: boolean;306 size?: 'sm' | 'md' | 'lg';307 variant?: 'primary' | 'secondary' | 'danger';308 }309310 let { label, type = 'button', disabled = false, size = 'md', variant = 'primary' }: Props = $props();311 </script>312313 <button314 {type}315 {disabled}316 class:disabled317 class:sm={size === 'sm'}318 class:md={size === 'md'}319 class:lg={size === 'lg'}320 class:primary={variant === 'primary'}321 class:secondary={variant === 'secondary'}322 class:danger={variant === 'danger'}323 on:click324 >325 {label}326 </button>327328 <style module>329 button {330 @apply px-4 py-2 rounded font-medium transition;331 }332 .primary { @apply bg-blue-600 text-white hover:bg-blue-700; }333 .secondary { @apply bg-gray-200 text-gray-800 hover:bg-gray-300; }334 .danger { @apply bg-red-600 text-white hover:bg-red-700; }335 .sm { @apply text-sm px-2 py-1; }336 .lg { @apply text-lg px-6 py-3; }337 .disabled { @apply opacity-50 cursor-not-allowed; }338 </style>339 ```3403413. **Key rules:**342 - All props must be typed (no `any`)343 - No API calls, no store mutations344 - Use TailwindCSS for styling345 - Emit events if needed: `on:click`, custom events via `dispatch()`346 - Props should be simple types or interfaces, not functions3473484. **Implement all atomic components before proceeding to feature components.**349350---351352### Step 6: Implement Feature and Page Components353354Implement feature components in `src/lib/components/features/`:3553561. **Feature components** (medium-complexity, domain-specific):357 ```svelte358 <script lang="ts">359 import { onMount } from 'svelte';360 import { mediaFilesStore } from '../../stores/mediaFiles';361 import * as api from '../../api/mediaFiles';362 import Button from '../atomic/Button.svelte';363 import MediaGrid from './MediaGallery/Grid.svelte';364365 let loading = false;366 let error: string | null = null;367368 async function handleUpload() {369 loading = true;370 error = null;371 try {372 const input = document.createElement('input');373 input.type = 'file';374 input.onchange = async (e) => {375 const file = (e.target as HTMLInputElement).files?.[0];376 if (file) {377 const result = await api.uploadFile(file);378 mediaFilesStore.addFile(result);379 }380 };381 input.click();382 } catch (err) {383 error = err instanceof Error ? err.message : 'Upload failed';384 } finally {385 loading = false;386 }387 }388389 onMount(async () => {390 try {391 await mediaFilesStore.fetchFiles();392 } catch (err) {393 error = err instanceof Error ? err.message : 'Failed to load files';394 }395 });396 </script>397398 <div class="space-y-4">399 {#if error}400 <div class="p-4 bg-red-100 text-red-800 rounded">401 {error}402 </div>403 {/if}404405 <Button label="Upload File" on:click={handleUpload} disabled={loading} />406407 {#if loading}408 <div class="text-center py-8">Loading...</div>409 {:else}410 <MediaGrid items={$mediaFilesStore.items} />411 {/if}412 </div>413 ```4144152. **Page components** in `src/routes/{route}/+page.svelte`:416 ```svelte417 <script lang="ts">418 import AppLayout from '../../lib/components/layout/AppLayout.svelte';419 import MediaUpload from '../../lib/components/features/MediaUpload.svelte';420 </script>421422 <AppLayout>423 <div class="container mx-auto">424 <h1 class="text-2xl font-bold mb-6">Media Library</h1>425 <MediaUpload />426 </div>427 </AppLayout>428 ```4294303. **Key principles:**431 - Feature components call API functions and update stores432 - Page components orchestrate layout and feature components433 - All async operations must have loading + error states434 - Use `onMount` to load page data435 - Use `$store` syntax to read store values reactively436437---438439### Step 7: Implement Loading and Error States440441For every API call in every component:4424431. **Create reactive state variables:**444 ```typescript445 let loading = false;446 let error: string | null = null;447 ```4484492. **Wrap API calls with try-catch-finally:**450 ```typescript451 async function fetchData() {452 loading = true;453 error = null;454 try {455 const data = await api.fetchData();456 store.update(s => ({ ...s, data }));457 } catch (err) {458 error = err instanceof Error ? err.message : 'An error occurred';459 } finally {460 loading = false;461 }462 }463 ```4644653. **Show loading UI while loading:**466 ```svelte467 {#if loading}468 <Skeleton />469 {:else if error}470 <ErrorMessage message={error} />471 {:else}472 <Content />473 {/if}474 ```4754764. **Disable form submissions while loading:**477 ```svelte478 <Button479 label="Submit"480 on:click={handleSubmit}481 disabled={loading}482 />483 ```484485---486487### Step 8: Write Tests and Verify4884891. **Create test files using Vitest + Svelte Testing Library:**490 ```typescript491 // tests/components/Button.test.ts492 import { describe, it, expect } from 'vitest';493 import { render, screen } from '@testing-library/svelte';494 import userEvent from '@testing-library/user-event';495 import Button from '../../src/lib/components/atomic/Button.svelte';496497 describe('Button', () => {498 it('renders with label', () => {499 render(Button, { props: { label: 'Click me' } });500 expect(screen.getByText('Click me')).toBeInTheDocument();501 });502503 it('dispatches click event', async () => {504 const { component } = render(Button, { props: { label: 'Click' } });505 let clicked = false;506 component.$on('click', () => { clicked = true; });507508 await userEvent.click(screen.getByRole('button'));509 expect(clicked).toBe(true);510 });511512 it('disables when disabled prop is true', () => {513 render(Button, { props: { label: 'Click', disabled: true } });514 expect(screen.getByRole('button')).toBeDisabled();515 });516 });517 ```5185192. **Test store actions:**520 ```typescript521 // tests/stores/mediaFiles.test.ts522 import { describe, it, expect } from 'vitest';523 import { mediaFilesStore, selectFile } from '../../src/lib/stores/mediaFiles';524525 describe('mediaFilesStore', () => {526 it('initializes with empty items', () => {527 let state;528 mediaFilesStore.subscribe(s => { state = s; });529 expect(state.items).toEqual([]);530 });531532 it('selectFile adds ID to selectedIds', () => {533 selectFile('file-123');534 let state;535 mediaFilesStore.subscribe(s => { state = s; });536 expect(state.selectedIds).toContain('file-123');537 });538 });539 ```5405413. **Run tests and verify they pass:**542 ```bash543 npm run test544 ```5455464. **Verify acceptance criteria:**547 - For each criterion in task-spec, verify the implementation satisfies it548 - Manual testing: navigate the app, test key workflows549 - Check for console errors or warnings550551---552553### Step 9: Final Verification5545551. **Run full test suite:**556 ```bash557 npm run test558 ```559 - All tests must pass.560 - No errors, no warnings.5615622. **Verify file structure matches frontend-design.file_structure:**563 - All specified directories exist564 - All specified files exist565 - No unexpected files5665673. **Verify acceptance criteria are met:**568 - For each criterion in task-spec.acceptance_criteria, verify the implementation satisfies it569 - Document any unmet criteria (should be none)5705714. **Check for TypeScript errors:**572 ```bash573 npm run check574 ```575 - No TypeScript errors or warnings5765775. **Check for build errors:**578 ```bash579 npm run build580 ```581 - Build succeeds without errors582583---584585## Post-Execution586587On successful completion:588589```590✓ Frontend implementation complete for TSK-NNN.591592Files created/modified:593 ✓ src/lib/types/media.ts (3 interfaces)594 ✓ src/lib/types/auth.ts (2 interfaces)595 ✓ src/lib/stores/mediaFiles.ts596 ✓ src/lib/stores/auth.ts597 ✓ src/lib/api/mediaFiles.ts (5 functions)598 ✓ src/lib/api/auth.ts (3 functions)599 ✓ src/lib/components/atomic/Button.svelte600 ✓ src/lib/components/atomic/TextField.svelte601 ✓ src/lib/components/layout/AppLayout.svelte602 ✓ src/lib/components/features/MediaGallery/Grid.svelte603 ✓ src/routes/+page.svelte604 ✓ src/routes/library/+page.svelte605 ✓ tests/ (N test files)606607Tests:608 npm run test result: 24 passed, 0 failed609610Build:611 npm run build: SUCCESS612613Acceptance criteria verified:614 ✓ Users can upload media files615 ✓ Users can browse media gallery616 ✓ Users can delete media files617 ✓ All API calls include auth headers618 ✓ All async operations show loading/error states619620Next: Deploy to production or proceed to next task phase621```622623---624625## Error Handling626627| Error | Action |628|-------|--------|629| `frontend-design.TSK-NNN.md` not found | Stop. Instruct agent: "Run /frontend-design first." |630| TypeScript compilation errors | Stop and report: "[list errors]". Agent must fix. |631| Test failures | Stop and report: "[list failed tests]". Agent must fix. |632| Missing prop typing | Stop and report: "[component] has untyped props". Agent must fix. |633| API call without auth header | Stop and report: "[function] missing auth header". Agent must fix. |634| Inline API calls in components | Stop and report: "[component] makes fetch directly". Refactor to API client. |635636---637638## References639640- **input.template.yaml**: Schema for frontend-design input, task-spec, backend-design641- **output.template.yaml**: Contract showing what was implemented642- **rules.md**: Mandatory conventions (must enforce during implementation)643644---645646## Compatibility Notes647648- **Framework:** Read `task-spec.execution_plan.tech_stack.frontend` to determine framework. This skill is optimized for **Svelte 5** but patterns apply to other frameworks (React, Vue, etc.).649- **Project setup:** Assumes SvelteKit (v2+) with TypeScript, Tailwind CSS, Vitest pre-configured.650- **API base:** Reads `VITE_API_BASE` environment variable; defaults to `http://localhost:3000`.651- **Auth:** Assumes JWT token in `authStore.token`; adjust for other auth methods.