Files
gh-marcioaltoe-claude-craft…/skills/frontend-engineer/SKILL.md
2025-11-30 08:39:03 +08:00

1073 lines
29 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
name: frontend-engineer
description: Expert frontend engineering with simplified pragmatic architecture, React 19, TanStack ecosystem, and Zustand state management. **ALWAYS use when implementing ANY frontend features.** Use when setting up project structure, creating pages and state management, designing gateway injection patterns, setting up HTTP communication and routing, organizing feature modules, or optimizing performance. **ALWAYS use when implementing Gateway Pattern (Interface + HTTP + Fake), Context API injection, Zustand stores, TanStack Router, or feature-based architecture.**
---
# Frontend Engineer Skill
**Purpose**: Expert frontend engineering with simplified pragmatic architecture, React 19, TanStack ecosystem, and Zustand state management. Provides implementation examples for building testable, maintainable, scalable frontend applications.
**When to Use**:
- Implementing frontend features and components
- Setting up project structure
- Creating pages and state management
- Designing gateway injection patterns
- Setting up HTTP communication and routing
- Organizing feature modules
- Performance optimization and code splitting
**NOTE**: For UI component design, Tailwind styling, shadcn/ui setup, responsive layouts, and accessibility implementation, defer to the `ui-designer` skill. This skill focuses on architecture, state management, and business logic orchestration.
---
## Documentation Lookup (MANDATORY)
**ALWAYS use MCP servers for up-to-date documentation:**
- **Context7 MCP**: Use for comprehensive library documentation, API reference, import statements, and version-specific patterns
- When user asks about TanStack Router, Query, Form, Table APIs
- For React 19 features and patterns
- For Vite configuration and build setup
- For Zustand API and patterns
- To verify correct import paths, hooks usage, and API patterns
- **Perplexity MCP**: Use for architectural research, design patterns, and best practices
- When researching pragmatic frontend architectures
- For state management strategies and trade-offs
- For performance optimization techniques
- For folder structure and code organization patterns
- For troubleshooting complex architectural issues
**Examples of when to use MCP:**
- "How to setup TanStack Router file-based routing?" → Use Context7 MCP for TanStack Router docs
- "What are React 19 use() hook patterns?" → Use Context7 MCP for React docs
- "How to use Zustand with TypeScript?" → Use Context7 MCP for Zustand docs
- "Best practices for feature-based architecture in React?" → Use Perplexity MCP for research
- "How to configure Vite with React 19?" → Use Context7 MCP for Vite docs
---
## Tech Stack
**For complete frontend tech stack details, see "Tech Stack > Frontend" section in CLAUDE.md**
**Quick Architecture Reference:**
This skill focuses on:
- Feature-based architecture (NOT Clean Architecture layers)
- Gateway Pattern (Interface + HTTP + Fake)
- Context API injection for testability
- Zustand for global client state
- TanStack Query for server state
### Testing:
- **Unit**: Vitest + React Testing Library
- **E2E**: Playwright
→ See `project-standards` skill for complete tech stack
→ See `ui-designer` skill for UI/UX specific technologies
→ See `docs/plans/2025-10-24-simplified-frontend-architecture-design.md` for architecture details
---
## Architecture Overview
Frontend follows a **simplified, pragmatic, feature-based architecture**.
**Core Principles:**
1. **Feature-based organization** - Code grouped by business functionality
2. **Pages as use cases** - Pages orchestrate business logic
3. **Externalized state** - Zustand stores are framework-agnostic, 100% testable
4. **Gateway injection** - Gateways injected via Context API for isolated testing
5. **YAGNI rigorously** - No unnecessary layers or abstractions
**Design Philosophy**: Simplicity and pragmatism over architectural purity. Remove Clean Architecture complexity (domain/application/infrastructure layers) in favor of direct, maintainable code.
### Recommended Structure
```
apps/web/src/
├── app/ # Application setup
│ ├── config/
│ │ ├── env.ts # Environment variables
│ │ └── index.ts
│ ├── providers/
│ │ ├── gateway-provider.tsx # Gateway injection (Context API)
│ │ ├── query-provider.tsx # TanStack Query setup
│ │ ├── theme-provider.tsx # Theme provider (shadcn/ui)
│ │ └── index.ts
│ ├── router.tsx # TanStack Router configuration
│ └── main.tsx # Application entry point
├── features/ # Feature modules (self-contained)
│ ├── auth/
│ │ ├── components/ # Pure UI components
│ │ │ ├── login-form.tsx
│ │ │ └── index.ts
│ │ ├── pages/ # Use cases - orchestrate logic
│ │ │ ├── login-page.tsx
│ │ │ └── index.ts
│ │ ├── stores/ # Zustand stores - testable entities
│ │ │ ├── auth-store.ts
│ │ │ └── index.ts
│ │ ├── gateways/ # External resource abstractions
│ │ │ ├── auth-gateway.ts # Interface + HTTP implementation
│ │ │ ├── auth-gateway.fake.ts # Fake for unit tests
│ │ │ └── index.ts
│ │ ├── hooks/ # Custom hooks (optional)
│ │ │ └── index.ts
│ │ ├── types/ # TypeScript types
│ │ │ ├── user.ts
│ │ │ └── index.ts
│ │ └── index.ts # Barrel file - public API
│ │
│ ├── dashboard/
│ └── profile/
├── shared/ # Shared code across features
│ ├── services/ # Global services
│ │ ├── http-api.ts # HTTP client base (Axios wrapper)
│ │ ├── storage.ts # LocalStorage/Cookie abstraction
│ │ └── index.ts
│ ├── components/
│ │ ├── ui/ # shadcn/ui components
│ │ └── layout/ # Shared layouts
│ ├── hooks/ # Global utility hooks
│ ├── lib/ # Utilities and helpers
│ │ ├── validators.ts # Zod schemas (common)
│ │ ├── formatters.ts
│ │ └── index.ts
│ └── types/ # Global types
├── routes/ # TanStack Router routes
│ ├── __root.tsx
│ ├── index.tsx
│ └── auth/
│ └── login.tsx
└── index.css
```
**Benefits**:
- ✅ Feature isolation - delete folder, remove feature
- ✅ No unnecessary layers - direct, maintainable code
- ✅ Testable business logic - Zustand stores are pure JS/TS
- ✅ Isolated page testing - inject fake gateways
- ✅ Clear separation - pages (orchestration), components (UI), stores (state)
---
## Layer Responsibilities (MANDATORY)
### 1. Shared Services Layer
**Purpose**: Reusable infrastructure used across features.
**Example - HTTP Client**:
```typescript
// shared/services/http-api.ts
import axios, { type AxiosInstance, type AxiosRequestConfig } from "axios";
export class HttpApi {
private client: AxiosInstance;
constructor(baseURL: string) {
this.client = axios.create({
baseURL,
timeout: 10000,
headers: { "Content-Type": "application/json" },
});
// Auto-inject auth token
this.client.interceptors.request.use((config) => {
const token = localStorage.getItem("auth_token");
if (token) config.headers.Authorization = `Bearer ${token}`;
return config;
});
// Handle 401 globally
this.client.interceptors.response.use(
(response) => response,
(error) => {
if (error.response?.status === 401) {
localStorage.removeItem("auth_token");
window.location.href = "/auth/login";
}
return Promise.reject(error);
}
);
}
async get<T>(url: string, config?: AxiosRequestConfig) {
const response = await this.client.get<T>(url, config);
return response.data;
}
async post<T>(url: string, data?: unknown, config?: AxiosRequestConfig) {
const response = await this.client.post<T>(url, data, config);
return response.data;
}
async put<T>(url: string, data?: unknown, config?: AxiosRequestConfig) {
const response = await this.client.put<T>(url, data, config);
return response.data;
}
async delete<T>(url: string, config?: AxiosRequestConfig) {
const response = await this.client.delete<T>(url, config);
return response.data;
}
}
// Singleton instance
export const httpApi = new HttpApi(import.meta.env.VITE_API_URL);
```
---
### 2. Gateway Layer
**Purpose**: Abstract external resource access. Enable testing with fakes.
**Example - Auth Gateway**:
```typescript
// features/auth/gateways/auth-gateway.ts
import { httpApi } from "@/shared/services/http-api";
import type { User } from "../types/user";
export interface LoginRequest {
email: string;
password: string;
}
export interface LoginResponse {
user: User;
token: string;
}
// Gateway interface (contract)
export interface AuthGateway {
login(request: LoginRequest): Promise<LoginResponse>;
logout(): Promise<void>;
getCurrentUser(): Promise<User>;
}
// Real implementation (HTTP)
export class AuthHttpGateway implements AuthGateway {
async login(request: LoginRequest): Promise<LoginResponse> {
return httpApi.post<LoginResponse>("/auth/login", request);
}
async logout(): Promise<void> {
await httpApi.post("/auth/logout");
}
async getCurrentUser(): Promise<User> {
return httpApi.get<User>("/auth/me");
}
}
// Fake implementation for unit tests
export class AuthFakeGateway implements AuthGateway {
private shouldFail = false;
setShouldFail(value: boolean) {
this.shouldFail = value;
}
async login(request: LoginRequest): Promise<LoginResponse> {
await new Promise((resolve) => setTimeout(resolve, 100));
if (this.shouldFail) {
throw new Error("Invalid credentials");
}
if (
request.email === "test@example.com" &&
request.password === "password123"
) {
return {
user: {
id: "1",
name: "Test User",
email: "test@example.com",
role: "user",
},
token: "fake-token-123",
};
}
throw new Error("Invalid credentials");
}
async logout(): Promise<void> {
await new Promise((resolve) => setTimeout(resolve, 50));
}
async getCurrentUser(): Promise<User> {
if (this.shouldFail) throw new Error("Unauthorized");
return {
id: "1",
name: "Test User",
email: "test@example.com",
role: "user",
};
}
}
```
---
### 3. Gateway Injection (Context API)
**Purpose**: Provide gateways to components. Allow override in tests.
**Example**:
```typescript
// app/providers/gateway-provider.tsx
import { createContext, useContext, type ReactNode } from "react";
import {
AuthGateway,
AuthHttpGateway,
} from "@/features/auth/gateways/auth-gateway";
interface Gateways {
authGateway: AuthGateway;
// Add more gateways as needed
}
const GatewayContext = createContext<Gateways | null>(null);
interface GatewayProviderProps {
children: ReactNode;
gateways?: Partial<Gateways>; // Allow override for tests
}
export function GatewayProvider({ children, gateways }: GatewayProviderProps) {
const defaultGateways: Gateways = {
authGateway: new AuthHttpGateway(),
};
const value = { ...defaultGateways, ...gateways };
return (
<GatewayContext.Provider value={value}>{children}</GatewayContext.Provider>
);
}
export function useGateways() {
const context = useContext(GatewayContext);
if (!context) {
throw new Error("useGateways must be used within GatewayProvider");
}
return context;
}
```
---
### 4. Store Layer (Zustand)
**Purpose**: Testable business logic, framework-agnostic state management.
**Example**:
```typescript
// features/auth/stores/auth-store.ts
import { create } from "zustand";
import type { User } from "../types/user";
interface AuthState {
user: User | null;
isAuthenticated: boolean;
isLoading: boolean;
error: string | null;
setUser: (user: User | null) => void;
setLoading: (loading: boolean) => void;
setError: (error: string | null) => void;
reset: () => void;
}
export const useAuthStore = create<AuthState>((set) => ({
user: null,
isAuthenticated: false,
isLoading: false,
error: null,
setUser: (user) => set({ user, isAuthenticated: user !== null }),
setLoading: (isLoading) => set({ isLoading }),
setError: (error) => set({ error }),
reset: () =>
set({ user: null, isAuthenticated: false, isLoading: false, error: null }),
}));
```
**Why Zustand:**
- ✅ Minimal boilerplate
- ✅ Excellent TypeScript support
- ✅ Framework-agnostic (100% testable without React)
- ✅ Large community and ecosystem
- ✅ Superior DX compared to alternatives
---
### 5. Page Layer (Use Cases)
**Purpose**: Orchestrate business logic by coordinating gateways, stores, and UI components.
**Example**:
```typescript
// features/auth/pages/login-page.tsx
import { useState } from "react";
import { useNavigate } from "@tanstack/react-router";
import { useGateways } from "@/app/providers/gateway-provider";
import { useAuthStore } from "../stores/auth-store";
import { LoginForm } from "../components/login-form";
export function LoginPage() {
const navigate = useNavigate();
const { authGateway } = useGateways(); // Injected gateway
const { setUser, setLoading, setError, isLoading, error } = useAuthStore();
const [formError, setFormError] = useState<string | null>(null);
const handleLogin = async (email: string, password: string) => {
setLoading(true);
setError(null);
setFormError(null);
try {
const { user, token } = await authGateway.login({ email, password });
localStorage.setItem("auth_token", token);
setUser(user);
navigate({ to: "/dashboard" });
} catch (err) {
const errorMessage = err instanceof Error ? err.message : "Login failed";
setError(errorMessage);
setFormError(errorMessage);
} finally {
setLoading(false);
}
};
return (
<div className="flex min-h-screen items-center justify-center">
<LoginForm
onSubmit={handleLogin}
isLoading={isLoading}
error={formError}
/>
</div>
);
}
```
---
### 6. Component Layer
**Purpose**: Pure UI components that receive props and emit events.
**Example**:
```typescript
// features/auth/components/login-form.tsx
import { useState } from "react";
import { Button } from "@/shared/components/ui/button";
import { Input } from "@/shared/components/ui/input";
interface LoginFormProps {
onSubmit: (email: string, password: string) => void;
isLoading?: boolean;
error?: string | null;
}
export function LoginForm({
onSubmit,
isLoading = false,
error,
}: LoginFormProps) {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
onSubmit(email, password);
};
return (
<form onSubmit={handleSubmit} className="w-full max-w-md space-y-4">
<div>
<Input
type="email"
placeholder="Email"
value={email}
onChange={(e) => setEmail(e.target.value)}
disabled={isLoading}
aria-label="Email"
/>
</div>
<div>
<Input
type="password"
placeholder="Password"
value={password}
onChange={(e) => setPassword(e.target.value)}
disabled={isLoading}
aria-label="Password"
/>
</div>
{error && <p className="text-sm text-red-600">{error}</p>}
<Button type="submit" disabled={isLoading} className="w-full">
{isLoading ? "Loading..." : "Login"}
</Button>
</form>
);
}
```
---
## State Management Strategy (MANDATORY)
**Use the RIGHT tool for each state type:**
### 1. Global Client State → Zustand
For application-wide client state (auth, theme, preferences):
```typescript
// shared/stores/theme-store.ts
import { create } from "zustand";
export type Theme = "light" | "dark" | "system";
interface ThemeState {
theme: Theme;
setTheme: (theme: Theme) => void;
}
export const useThemeStore = create<ThemeState>((set) => ({
theme: "system",
setTheme: (theme) => set({ theme }),
}));
```
### 2. Server State → TanStack Query
For data from backend APIs with caching, revalidation, and synchronization:
```typescript
// features/users/hooks/use-users-query.ts
import { useQuery } from "@tanstack/react-query";
import { useGateways } from "@/app/providers/gateway-provider";
export function useUsersQuery() {
const { userGateway } = useGateways();
return useQuery({
queryKey: ["users"],
queryFn: () => userGateway.getUsers(),
staleTime: 5 * 60 * 1000, // 5 minutes
});
}
```
### 3. Form State → TanStack Form
For form management with validation:
```typescript
import { useForm } from "@tanstack/react-form";
import { zodValidator } from "@tanstack/zod-form-adapter";
import { z } from "zod";
const userSchema = z.object({
name: z.string().min(2),
email: z.string().email(),
});
export function UserForm() {
const form = useForm({
defaultValues: { name: "", email: "" },
validatorAdapter: zodValidator(),
validators: { onChange: userSchema },
onSubmit: async ({ value }) => console.log(value),
});
// ... render form
}
```
### 4. URL State → TanStack Router
For URL parameters and search params:
```typescript
// routes/users/$userId.tsx
import { createFileRoute } from "@tanstack/react-router";
import { z } from "zod";
const userSearchSchema = z.object({
tab: z.enum(["profile", "settings"]).optional(),
});
export const Route = createFileRoute("/users/$userId")({
validateSearch: userSearchSchema,
component: UserDetail,
});
function UserDetail() {
const { userId } = Route.useParams();
const { tab = "profile" } = Route.useSearch();
return (
<div>
User {userId} - Tab: {tab}
</div>
);
}
```
### 5. Local Component State → useState/useReducer
For component-specific state:
```typescript
export function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
```
---
## Testing Strategy (MANDATORY)
### Test Pyramid
```
┌─────────────┐
E2E Tests ╲ Few - Critical flows
(Playwright) ╲
╱─────────────────────╲
Integration Tests ╲ Some - Pages with fakes
(Pages + Stores) ╲
╱─────────────────────────╲
Unit Tests ╲ Many - Stores, utils
(Stores, Utils, Comps) ╲
─────────────────────────────
```
### Store Tests (Unit - High Priority)
Zustand stores are 100% testable without React:
```typescript
// features/auth/stores/auth-store.test.ts
import { describe, it, expect, beforeEach } from "bun:test";
import { useAuthStore } from "./auth-store";
describe("AuthStore", () => {
beforeEach(() => {
useAuthStore.getState().reset();
});
it("should start with empty state", () => {
const state = useAuthStore.getState();
expect(state.user).toBeNull();
expect(state.isAuthenticated).toBe(false);
});
it("should update user and isAuthenticated when setting user", () => {
const user = {
id: "1",
name: "Test",
email: "test@example.com",
role: "user",
};
useAuthStore.getState().setUser(user);
expect(useAuthStore.getState().user).toEqual(user);
expect(useAuthStore.getState().isAuthenticated).toBe(true);
});
});
```
### Page Tests (Integration with Fake Gateways)
Test orchestration logic with injected fakes:
```typescript
// features/auth/pages/login-page.test.tsx
import { describe, it, expect, beforeEach } from "bun:test";
import { render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { GatewayProvider } from "@/app/providers/gateway-provider";
import { AuthFakeGateway } from "../gateways/auth-gateway";
import { LoginPage } from "./login-page";
import { useAuthStore } from "../stores/auth-store";
describe("LoginPage", () => {
let fakeAuthGateway: AuthFakeGateway;
beforeEach(() => {
fakeAuthGateway = new AuthFakeGateway();
useAuthStore.getState().reset();
});
it("should login successfully and update store", async () => {
const user = userEvent.setup();
render(
<GatewayProvider gateways={{ authGateway: fakeAuthGateway }}>
<LoginPage />
</GatewayProvider>
);
await user.type(screen.getByLabelText(/email/i), "test@example.com");
await user.type(screen.getByLabelText(/password/i), "password123");
await user.click(screen.getByRole("button", { name: /login/i }));
await waitFor(() => {
expect(useAuthStore.getState().isAuthenticated).toBe(true);
});
});
it("should show error when login fails", async () => {
const user = userEvent.setup();
fakeAuthGateway.setShouldFail(true);
render(
<GatewayProvider gateways={{ authGateway: fakeAuthGateway }}>
<LoginPage />
</GatewayProvider>
);
await user.type(screen.getByLabelText(/email/i), "wrong@example.com");
await user.type(screen.getByLabelText(/password/i), "wrong");
await user.click(screen.getByRole("button", { name: /login/i }));
await waitFor(() => {
expect(screen.getByText(/invalid credentials/i)).toBeInTheDocument();
});
});
});
```
### Component Tests (Unit - Pure UI)
```typescript
// features/auth/components/login-form.test.tsx
import { describe, it, expect, mock } from "bun:test";
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { LoginForm } from "./login-form";
describe("LoginForm", () => {
it("should render email and password fields", () => {
render(<LoginForm onSubmit={mock()} />);
expect(screen.getByLabelText(/email/i)).toBeInTheDocument();
expect(screen.getByLabelText(/password/i)).toBeInTheDocument();
});
it("should call onSubmit with correct values", async () => {
const user = userEvent.setup();
const onSubmit = mock();
render(<LoginForm onSubmit={onSubmit} />);
await user.type(screen.getByLabelText(/email/i), "test@example.com");
await user.type(screen.getByLabelText(/password/i), "password123");
await user.click(screen.getByRole("button", { name: /login/i }));
expect(onSubmit).toHaveBeenCalledWith("test@example.com", "password123");
});
});
```
### E2E Tests (Playwright - Critical Flows)
```typescript
// e2e/auth/login.spec.ts
import { test, expect } from "@playwright/test";
test.describe("Login Flow", () => {
test("should login and redirect to dashboard", async ({ page }) => {
await page.goto("/auth/login");
await page.fill('input[name="email"]', "test@example.com");
await page.fill('input[name="password"]', "password123");
await page.click('button:has-text("Login")');
await expect(page).toHaveURL("/dashboard");
await expect(page.locator("text=Welcome, Test User")).toBeVisible();
});
});
```
---
## Naming Conventions (MANDATORY)
### Files and Folders
- **Files**: `kebab-case` with suffixes
- **Folders**: `kebab-case`
```
features/auth/
├── components/
│ ├── login-form.tsx # Component: kebab-case
│ └── index.ts
├── pages/
│ ├── login-page.tsx # Suffix: -page.tsx
│ └── index.ts
├── stores/
│ ├── auth-store.ts # Suffix: -store.ts
│ └── index.ts
├── gateways/
│ ├── auth-gateway.ts # Suffix: -gateway.ts
│ ├── auth-gateway.fake.ts # Suffix: -gateway.fake.ts
│ └── index.ts
```
### Code Elements
**Components**:
```typescript
// ✅ Good
export function LoginForm({ onSubmit }: LoginFormProps) {}
// ❌ Avoid
export const loginForm = () => {}; // No arrow function exports
export function Form() {} // Too generic
```
**Pages**:
```typescript
// ✅ Good - "Page" suffix
export function LoginPage() {}
// ❌ Avoid
export function Login() {} // Confuses with component
```
**Stores**:
```typescript
// ✅ Good - "use" + name + "Store"
export const useAuthStore = create<AuthState>(...);
// ❌ Avoid
export const authStore = create(...); // Missing "use" prefix
```
**Gateways**:
```typescript
// ✅ Good - No "I" prefix
export interface AuthGateway {}
export class AuthHttpGateway implements AuthGateway {}
export class AuthFakeGateway implements AuthGateway {}
// ❌ Avoid
export interface IAuthGateway {} // No "I" prefix
```
---
## TanStack Router Patterns (MANDATORY)
### File-Based Routing
```typescript
// routes/__root.tsx
import { createRootRoute, Outlet } from "@tanstack/react-router";
export const Route = createRootRoute({
component: () => (
<div>
<nav>Navigation</nav>
<Outlet />
</div>
),
});
// routes/index.tsx
import { createFileRoute } from "@tanstack/react-router";
export const Route = createFileRoute("/")({
component: HomePage,
});
function HomePage() {
return <h1>Home</h1>;
}
```
### Type-Safe Navigation
```typescript
import { Link, useNavigate } from "@tanstack/react-router";
export function Navigation() {
const navigate = useNavigate();
return (
<div>
<Link to="/users/$userId" params={{ userId: "123" }}>
View User
</Link>
<button
onClick={() => {
navigate({ to: "/users/$userId", params: { userId: "456" } });
}}
>
Go to User
</button>
</div>
);
}
```
---
## Component Organization Principles (MANDATORY)
### 1. Keep Components Small (< 150 lines)
```typescript
// ✅ Good - Extract into smaller components
export function UserDashboard() {
return (
<div>
<UserHeader />
<UserStats />
<UserActivity />
</div>
);
}
```
### 2. Extract Logic into Hooks (< 20 lines in component)
```typescript
// ✅ Good
export function UserList() {
const { users, loading, error } = useUsers();
if (loading) return <Loading />;
if (error) return <Error />;
return <div>{/* Render users */}</div>;
}
```
### 3. One Component Per File
```typescript
// ✅ Good - Separate files
// user-card.tsx
export function UserCard() {}
// user-avatar.tsx
export function UserAvatar() {}
```
---
## Critical Rules
**NEVER:**
- Use Clean Architecture layers (domain/application/infrastructure)
- Import stores in components (pass data via props)
- Call HTTP directly from components (use gateways)
- Use `any` type (use `unknown` with type guards)
- Skip TypeScript types
- Use npm, pnpm, yarn (use Bun)
- Create components > 150 lines
- Keep logic > 20 lines in components
**ALWAYS:**
- Organize by features (feature-based structure)
- Pages orchestrate logic (use gateways + stores)
- Externalize state to Zustand stores
- Inject gateways via Context API
- Write tests for stores (unit) and pages (integration with fakes)
- Use functional components with TypeScript
- One component per file
- Extract logic into custom hooks
- Use Bun for all package management
- Run `bun run craft` after creating/moving files
---
## Deliverables
When helping users, provide:
1. **Feature Folder Structure**: Organized with pages/, components/, stores/, gateways/
2. **Gateway Definitions**: Interface + HTTP implementation + Fake implementation
3. **Gateway Provider**: Context API setup for dependency injection
4. **Zustand Stores**: Framework-agnostic state management with actions
5. **Pages**: Orchestration logic using gateways and stores
6. **Components**: Pure UI components with props
7. **Router Configuration**: TanStack Router setup with type safety
8. **Test Examples**: Store tests, page tests with fakes, component tests, E2E
9. **Configuration Files**: Vite, TypeScript, TanStack configurations
---
## Summary
Frontend architecture focuses on:
1. **Simplicity**: No unnecessary layers, direct code, pragmatic patterns
2. **Testability**: Zustand stores (pure), pages with fake gateways, isolated components
3. **Maintainability**: Feature-based structure, clear responsibilities, consistent patterns
4. **Type Safety**: Strong typing, Zod validation, TanStack Router
5. **Performance**: Code splitting, lazy loading, optimistic updates
6. **User Experience**: Error handling, loading states, toast notifications
**Remember**: Good architecture makes change easy. Build systems that are simple, testable, and maintainable. Avoid over-engineering. Focus on delivering value.
---
**Reference**:
- Architecture design: `docs/plans/2025-10-24-simplified-frontend-architecture-design.md`
- Project standards: `project-standards` skill
- UI/UX: `ui-designer` skill