Install
$ agentstack add skill-marcioaltoe-claude-craftkit-frontend-engineer ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo 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.
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:
- Feature-based organization - Code grouped by business functionality
- Pages as use cases - Pages orchestrate business logic
- Externalized state - Zustand stores are framework-agnostic, 100% testable
- Gateway injection - Gateways injected via Context API for isolated testing
- 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.
Write a review
Versions
- v0.1.0 Imported from the upstream source.