AgentStack
SKILL verified MIT Self-run

Frontend Engineer

skill-marcioaltoe-claude-craftkit-frontend-engineer · by marcioaltoe

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

No reviews yet
0 installs
2 views
0.0% view→install

Install

$ agentstack add skill-marcioaltoe-claude-craftkit-frontend-engineer

✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access Used
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets Used
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

Are you the author of Frontend Engineer? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

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:

// 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(url: string, config?: AxiosRequestConfig) {
    const response = await this.client.get(url, config);
    return response.data;
  }

  async post(url: string, data?: unknown, config?: AxiosRequestConfig) {
    const response = await this.client.post(url, data, config);
    return response.data;
  }

  async put(url: string, data?: unknown, config?: AxiosRequestConfig) {
    const response = await this.client.put(url, data, config);
    return response.data;
  }

  async delete(url: string, config?: AxiosRequestConfig) {
    const response = await this.client.delete(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:

// 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;
  logout(): Promise;
  getCurrentUser(): Promise;
}

// Real implementation (HTTP)
export class AuthHttpGateway implements AuthGateway {
  async login(request: LoginRequest): Promise {
    return httpApi.post("/auth/login", request);
  }

  async logout(): Promise {
    await httpApi.post("/auth/logout");
  }

  async getCurrentUser(): Promise {
    return httpApi.get("/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 {
    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 {
    await new Promise((resolve) => setTimeout(resolve, 50));
  }

  async getCurrentUser(): Promise {
    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:

// 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(null);

interface GatewayProviderProps {
  children: ReactNode;
  gateways?: Partial; // Allow override for tests
}

export function GatewayProvider({ children, gateways }: GatewayProviderProps) {
  const defaultGateways: Gateways = {
    authGateway: new AuthHttpGateway(),
  };

  const value = { ...defaultGateways, ...gateways };

  return (
    {children}
  );
}

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:

// 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((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:

// 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(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 (
    
      
    
  );
}

6. Component Layer

Purpose: Pure UI components that receive props and emit events.

Example:

// 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 (
    
      
         setEmail(e.target.value)}
          disabled={isLoading}
          aria-label="Email"
        />
      

      
         setPassword(e.target.value)}
          disabled={isLoading}
          aria-label="Password"
        />
      

      {error && {error}}

      
        {isLoading ? "Loading..." : "Login"}
      
    
  );
}

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):

// 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((set) => ({
  theme: "system",
  setTheme: (theme) => set({ theme }),
}));

2. Server State → TanStack Query

For data from backend APIs with caching, revalidation, and synchronization:

// 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:

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 }

…

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [marcioaltoe](https://github.com/marcioaltoe)
- **Source:** [marcioaltoe/claude-craftkit](https://github.com/marcioaltoe/claude-craftkit)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet — be the first.

Versions

  • v0.1.0 Imported from the upstream source.