# Frontend Engineer

> 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.**

- Skill: `marcioaltoe/frontend-engineer` (Agent Skill)
- Install (CLI): `npx skillmds@latest add marcioaltoe/frontend-engineer`
- Raw SKILL.md: https://api.skillmd.com/api/skills/marcioaltoe/frontend-engineer/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: marcioaltoe (https://skillmd.com/u/marcioaltoe)
- Updated: 2026-09-10
- Page: https://skillmd.com/skills/marcioaltoe/frontend-engineer

---


# 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

