agentsclimarketplace

Fe main

Skill gridi-ai/gridi-skills/skills/fe-main

A skill for developing frontends based on design specs (design-spec.md), Figma designs, and OpenAPI specs. Produces UI publishing, component development, and API integration code. Use this skill for frontend development, publishing, or UI implementation requests.From its SKILL.md

Install
npx -y skills add gridi-ai/gridi-skills --skill fe-main

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

28.5 KB, as published. Nobody here has run it

🌐 Language

All output documents and user-facing messages must be written in the language specified by crew-config.json β†’ preferences.language. If not set, default to English.

πŸ”§ Project Configuration Reference

You must read crew-config.json first and operate according to the project settings.

  • frontend.framework: Next.js, React+Vite, Vue, Svelte, etc.
  • frontend.styling: Tailwind, styled-components, CSS Modules, etc.
  • frontend.stateManagement: Zustand, Redux, etc.
  • conventions.i18n: Whether i18n is used (if false, hardcoded strings are allowed)
  • integrations.figma: Whether Figma MCP is used

If crew-config.json does not exist, guide the user to run the /project-init skill first.

Frontend Main Developer

Develops the frontend based on design specs (design-spec.md), Figma designs (if available), and OpenAPI specs.

Use Generated API Clients

Required: All API calls must use generated API clients instead of manual fetch/axios calls. Generate API clients from the OpenAPI spec using the project's configured tool (check crew-config.json or project documentation for the specific generator).

Core Rules

  1. Direct axios/fetch calls prohibited: Direct calls like axios.get(), fetch() are prohibited when a generated client exists
  2. Use only generated clients: Use the generated API client methods for all API calls
  3. Do not modify generated code: Do not directly edit auto-generated files
  4. Use generated types: Import types from the generated API client instead of defining them manually

Usage Example

// βœ… GOOD: Using generated API client
import { usersApiClient } from './api/client';
import type { User } from './api/generated/models';

const { data } = await usersApiClient.getUser({ id });

// ❌ BAD: Direct fetch call
const res = await fetch(`/api/users/${id}`);

// ❌ BAD: Local interface definition (when generated type exists)
interface User { id: string; name: string; ... }

When the OpenAPI Spec Changes

# Regenerate API client code (check your project's package.json for the exact script name)
npm run api:generate

# Check for broken imports with type check
npx tsc --noEmit

Internationalization (i18n)

Check crew-config.json β†’ conventions.i18n to determine if i18n is required.

  • If i18n is enabled: All UI text must use the project's translation mechanism
  • If i18n is disabled: Hardcoded strings are allowed

Workflow

1. Verify Input Documents

Receive the following documents as input:

Required Input

  • design-spec.md: Design system, component specifications, screen layout definitions
  • OpenAPI spec: API endpoints, request/response type definitions

Optional Input

  • Figma design: Referenced when Figma links are included in design-spec.md
  • Wireframe: For screen structure reference
Example inputs:
- "Implement the login page based on docs/{backlog-keyword}/design-spec.md"
- "Connect the API according to the OpenAPI spec"
- "Run npm run api:generate and connect the form using the generated hooks"

Important: When the OpenAPI spec changes, you must run npm run api:generate to regenerate types and hooks.

1.1 Design Reference Priority

PriorityReferenceUsage
1Figma design (if available)Actual UI implementation (colors, spacing, layout)
2design-spec.mdDesign tokens, component specs, state-specific behavior
3WireframeScreen structure and flow reference

Note: When a Figma design is available, check the Figma links and component keys in design-spec.md and reference the actual design values. When Figma is unavailable, use the CSS/design token values from design-spec.md directly.

2. Project Analysis

  1. Understand existing project structure
  2. Identify the framework in use
  3. Check UI library/design system
  4. Understand state management approach

Supported Frameworks

FrameworkUI LibraryAPI ClientClient State
Next.jsTailwind, shadcn/ui, Radix UIProject-configured generatorZustand
ReactTailwind, shadcn/ui, MUIReact Query (orval, openapi-generator-cli, etc.)Zustand
VueVuetify, Element PlusVue Query (orval, openapi-generator-cli, etc.)Pinia
SvelteSkeletonSvelte Query (orval, openapi-generator-cli, etc.)Svelte Store

Check crew-config.json β†’ frontend.framework and project documentation to determine which API client generation tool is in use.

3. Code Structure

3.1 Directory Structure (React/Next.js)

src/
β”œβ”€β”€ app/                    # App Router (Next.js)
β”‚   β”œβ”€β”€ (auth)/
β”‚   β”‚   β”œβ”€β”€ login/
β”‚   β”‚   β”‚   └── page.tsx
β”‚   β”‚   └── signup/
β”‚   β”‚       └── page.tsx
β”‚   β”œβ”€β”€ layout.tsx
β”‚   └── providers.tsx       # React Query Provider, etc.
β”œβ”€β”€ api/                    # API related (OpenAPI generated)
β”‚   β”œβ”€β”€ custom-instance.ts  # Custom axios instance
β”‚   β”œβ”€β”€ generated/          # Auto-generated by orval (do not modify)
β”‚   β”‚   β”œβ”€β”€ auth/
β”‚   β”‚   β”‚   └── auth.ts
β”‚   β”‚   β”œβ”€β”€ users/
β”‚   β”‚   β”‚   └── users.ts
β”‚   β”‚   └── index.ts
β”‚   └── model/              # Auto-generated types (do not modify)
β”‚       β”œβ”€β”€ index.ts
β”‚       β”œβ”€β”€ loginRequest.ts
β”‚       └── userResponse.ts
β”œβ”€β”€ components/             # Components
β”‚   β”œβ”€β”€ ui/                 # Base UI components
β”‚   β”‚   β”œβ”€β”€ button.tsx
β”‚   β”‚   └── input.tsx
β”‚   β”œβ”€β”€ forms/              # Form components
β”‚   β”‚   └── login-form.tsx
β”‚   └── layouts/            # Layout components
β”‚       └── header.tsx
β”œβ”€β”€ hooks/                  # Custom hooks (wrapping generated hooks)
β”‚   β”œβ”€β”€ use-auth.ts
β”‚   └── use-form.ts
β”œβ”€β”€ lib/                    # Utilities
β”‚   β”œβ”€β”€ schemas/            # Zod schemas (for form validation)
β”‚   β”‚   └── auth.ts
β”‚   └── utils.ts
β”œβ”€β”€ stores/                 # State management (Zustand)
β”‚   └── auth-store.ts
└── styles/                 # Styles
    └── globals.css

# Root configuration files
β”œβ”€β”€ orval.config.ts         # OpenAPI client generation config
└── docs/
    └── openapi.yaml        # OpenAPI spec file

3.2 Page Component

// app/(auth)/login/page.tsx
import { LoginForm } from '@/components/forms/login-form';
import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card';

export default function LoginPage() {
  return (
    <div className="min-h-screen flex items-center justify-center bg-gray-50">
      <Card className="w-full max-w-md">
        <CardHeader className="text-center">
          <Logo className="mx-auto mb-4" />
          <CardTitle>Log In</CardTitle>
        </CardHeader>
        <CardContent>
          <LoginForm />
        </CardContent>
      </Card>
    </div>
  );
}

3.3 Form Component

// components/forms/login-form.tsx
'use client';

import { useState } from 'react';
import { useRouter } from 'next/navigation';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { useAuth } from '@/hooks/use-auth';
import { loginSchema } from '@/lib/schemas/auth';

type LoginFormData = z.infer<typeof loginSchema>;

export function LoginForm() {
  const router = useRouter();
  const { login, isLoading } = useAuth();
  const [error, setError] = useState<string | null>(null);

  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm<LoginFormData>({
    resolver: zodResolver(loginSchema),
  });

  const onSubmit = async (data: LoginFormData) => {
    try {
      setError(null);
      await login(data);
      router.push('/dashboard');
    } catch (err) {
      setError(err instanceof Error ? err.message : 'Login failed.');
    }
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
      {error && (
        <div className="p-3 text-sm text-red-500 bg-red-50 rounded-md">
          {error}
        </div>
      )}

      <div className="space-y-2">
        <label htmlFor="email" className="text-sm font-medium">
          Email
        </label>
        <Input
          id="email"
          type="email"
          placeholder="[email protected]"
          {...register('email')}
          error={errors.email?.message}
        />
      </div>

      <div className="space-y-2">
        <label htmlFor="password" className="text-sm font-medium">
          Password
        </label>
        <Input
          id="password"
          type="password"
          placeholder="β€’β€’β€’β€’β€’β€’β€’β€’"
          {...register('password')}
          error={errors.password?.message}
        />
      </div>

      <Button type="submit" className="w-full" disabled={isLoading}>
        {isLoading ? 'Logging in...' : 'Log In'}
      </Button>

      <div className="text-center text-sm text-gray-500">
        <a href="/forgot-password" className="hover:underline">
          Forgot Password
        </a>
        {' | '}
        <a href="/signup" className="hover:underline">
          Sign Up
        </a>
      </div>
    </form>
  );
}

4. API Integration (OpenAPI Generated Client)

4.1 OpenAPI Client Generation Setup

Use the project's configured API client generation tool to auto-generate types and API hooks/clients from the OpenAPI spec. Check crew-config.json or the project's package.json scripts for the specific tool in use.

Example: orval + React Query (when using React)
npm install @tanstack/react-query axios
npm install -D orval
// orval.config.ts
import { defineConfig } from 'orval';

export default defineConfig({
  api: {
    input: {
      target: './docs/openapi.yaml',  // OpenAPI spec file path
    },
    output: {
      mode: 'tags-split',             // Split files by tag
      target: './src/api/generated',  // Generated file location
      schemas: './src/api/model',     // Type definition location
      client: 'react-query',          // Generate React Query hooks
      httpClient: 'axios',            // Use axios
      mock: false,
      override: {
        mutator: {
          path: './src/api/custom-instance.ts',  // Custom axios instance
          name: 'customInstance',
        },
        query: {
          useQuery: true,
          useMutation: true,
          signal: true,
        },
      },
    },
  },
});
{
  "scripts": {
    "api:generate": "orval",
    "api:watch": "orval --watch"
  }
}

Other common tools include openapi-generator-cli (typescript-axios, typescript-fetch), swagger-codegen, and openapi-typescript. Use whichever tool your project has configured.

4.2 Custom Axios Instance

// src/api/custom-instance.ts
import Axios, { AxiosRequestConfig, AxiosError } from 'axios';
import { useAuthStore } from '@/stores/auth-store';

export const AXIOS_INSTANCE = Axios.create({
  baseURL: process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3000/api/v1',
});

// Request interceptor: auto-attach token
AXIOS_INSTANCE.interceptors.request.use((config) => {
  const token = useAuthStore.getState().token;
  if (token) {
    config.headers.Authorization = `Bearer ${token}`;
  }
  return config;
});

// Response interceptor: handle token expiration
AXIOS_INSTANCE.interceptors.response.use(
  (response) => response,
  async (error: AxiosError) => {
    if (error.response?.status === 401) {
      useAuthStore.getState().clearToken();
      window.location.href = '/login';
    }
    return Promise.reject(error);
  }
);

// Custom instance function for orval
export const customInstance = <T>(config: AxiosRequestConfig): Promise<T> => {
  const source = Axios.CancelToken.source();
  const promise = AXIOS_INSTANCE({
    ...config,
    cancelToken: source.token,
  }).then(({ data }) => data);

  // @ts-expect-error cancel property for react-query
  promise.cancel = () => {
    source.cancel('Query was cancelled');
  };

  return promise;
};

export default customInstance;

4.3 Generated Code Structure

Structure generated after running orval:

src/api/
β”œβ”€β”€ custom-instance.ts          # Custom axios instance
β”œβ”€β”€ generated/                  # Auto-generated files
β”‚   β”œβ”€β”€ auth/
β”‚   β”‚   └── auth.ts            # Authentication-related hooks
β”‚   β”œβ”€β”€ users/
β”‚   β”‚   └── users.ts           # User-related hooks
β”‚   └── index.ts               # Full exports
└── model/                      # Auto-generated types
    β”œβ”€β”€ loginRequest.ts
    β”œβ”€β”€ signupRequest.ts
    β”œβ”€β”€ userResponse.ts
    β”œβ”€β”€ tokenResponse.ts
    └── index.ts

4.4 Generated Hook Usage Example

// Auto-generated hooks (src/api/generated/auth/auth.ts)
// This file is auto-generated by orval

import { useMutation, useQuery } from '@tanstack/react-query';
import type { LoginRequest, TokenResponse, UserResponse } from '../../model';
import { customInstance } from '../../custom-instance';

// POST /auth/login
export const useLogin = () => {
  return useMutation<TokenResponse, Error, LoginRequest>({
    mutationFn: (data) => customInstance({
      url: '/auth/login',
      method: 'POST',
      data,
    }),
  });
};

// POST /auth/signup
export const useSignup = () => {
  return useMutation<UserResponse, Error, SignupRequest>({
    mutationFn: (data) => customInstance({
      url: '/auth/signup',
      method: 'POST',
      data,
    }),
  });
};

// GET /auth/me
export const useGetMe = (options?: { enabled?: boolean }) => {
  return useQuery<UserResponse, Error>({
    queryKey: ['auth', 'me'],
    queryFn: () => customInstance({ url: '/auth/me', method: 'GET' }),
    ...options,
  });
};

// POST /auth/logout
export const useLogout = () => {
  return useMutation<void, Error>({
    mutationFn: () => customInstance({
      url: '/auth/logout',
      method: 'POST',
    }),
  });
};

4.5 Using Generated Hooks in Components

// hooks/use-auth.ts - Wrapping generated hooks to add business logic
import { useQueryClient } from '@tanstack/react-query';
import { useLogin, useLogout, useGetMe } from '@/api/generated/auth/auth';
import { useAuthStore } from '@/stores/auth-store';

export function useAuth() {
  const queryClient = useQueryClient();
  const { setToken, clearToken, token } = useAuthStore();

  // Using generated hooks
  const loginMutation = useLogin();
  const logoutMutation = useLogout();
  const { data: user, isLoading: isLoadingUser } = useGetMe({
    enabled: !!token,
  });

  const login = async (data: Parameters<typeof loginMutation.mutateAsync>[0]) => {
    const result = await loginMutation.mutateAsync(data);
    setToken(result.accessToken);
    queryClient.invalidateQueries({ queryKey: ['auth', 'me'] });
    return result;
  };

  const logout = async () => {
    await logoutMutation.mutateAsync();
    clearToken();
    queryClient.clear();
  };

  return {
    user: user?.data,
    isLoading: loginMutation.isPending,
    isLoadingUser,
    isAuthenticated: !!token,
    login,
    logout,
    error: loginMutation.error,
  };
}

4.6 Using in Form Components

// components/forms/login-form.tsx
'use client';

import { useState } from 'react';
import { useRouter } from 'next/navigation';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { useAuth } from '@/hooks/use-auth';
import type { LoginRequest } from '@/api/model';  // Using auto-generated types

const loginSchema = z.object({
  email: z.string().email('Please enter a valid email'),
  password: z.string().min(8, 'Password must be at least 8 characters'),
});

export function LoginForm() {
  const router = useRouter();
  const { login, isLoading, error } = useAuth();

  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm<LoginRequest>({
    resolver: zodResolver(loginSchema),
  });

  const onSubmit = async (data: LoginRequest) => {
    try {
      await login(data);
      router.push('/dashboard');
    } catch {
      // Error is handled via useAuth's error state
    }
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
      {error && (
        <div className="p-3 text-sm text-red-500 bg-red-50 rounded-md">
          {error.message}
        </div>
      )}

      <div className="space-y-2">
        <label htmlFor="email" className="text-sm font-medium">
          Email
        </label>
        <Input
          id="email"
          type="email"
          placeholder="[email protected]"
          {...register('email')}
          error={errors.email?.message}
        />
      </div>

      <div className="space-y-2">
        <label htmlFor="password" className="text-sm font-medium">
          Password
        </label>
        <Input
          id="password"
          type="password"
          placeholder="β€’β€’β€’β€’β€’β€’β€’β€’"
          {...register('password')}
          error={errors.password?.message}
        />
      </div>

      <Button type="submit" className="w-full" isLoading={isLoading}>
        Log In
      </Button>
    </form>
  );
}

4.7 CRUD Example (Using Generated Hooks)

// pages/users/index.tsx - List retrieval
import { useGetUsers } from '@/api/generated/users/users';

export function UserList() {
  const { data, isLoading, error } = useGetUsers({
    page: 1,
    limit: 10,
  });

  if (isLoading) return <Skeleton />;
  if (error) return <ErrorMessage error={error} />;

  return (
    <ul>
      {data?.data.map((user) => (
        <li key={user.id}>{user.email}</li>
      ))}
    </ul>
  );
}
// pages/users/[id].tsx - Detail retrieval
import { useGetUser } from '@/api/generated/users/users';

export function UserDetail({ userId }: { userId: string }) {
  const { data, isLoading } = useGetUser(userId);

  if (isLoading) return <Skeleton />;

  return <div>{data?.data.email}</div>;
}
// components/forms/user-form.tsx - Create/Update
import { useCreateUser, useUpdateUser } from '@/api/generated/users/users';
import type { CreateUserRequest, UpdateUserRequest } from '@/api/model';

export function UserForm({ userId }: { userId?: string }) {
  const createMutation = useCreateUser();
  const updateMutation = useUpdateUser();
  const queryClient = useQueryClient();

  const onSubmit = async (data: CreateUserRequest | UpdateUserRequest) => {
    if (userId) {
      await updateMutation.mutateAsync({ id: userId, data });
    } else {
      await createMutation.mutateAsync(data);
    }
    queryClient.invalidateQueries({ queryKey: ['users'] });
  };

  // ... form UI
}

4.8 React Query Provider Setup

// app/providers.tsx
'use client';

import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
import { useState } from 'react';

export function Providers({ children }: { children: React.ReactNode }) {
  const [queryClient] = useState(
    () =>
      new QueryClient({
        defaultOptions: {
          queries: {
            staleTime: 60 * 1000,        // Stay fresh for 1 minute
            gcTime: 5 * 60 * 1000,       // Keep cache for 5 minutes
            retry: 1,
            refetchOnWindowFocus: false,
          },
        },
      })
  );

  return (
    <QueryClientProvider client={queryClient}>
      {children}
      <ReactQueryDevtools initialIsOpen={false} />
    </QueryClientProvider>
  );
}
// app/layout.tsx
import { Providers } from './providers';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="ko">
      <body>
        <Providers>{children}</Providers>
      </body>
    </html>
  );
}

5. UI Components

5.1 Button Component

// components/ui/button.tsx
import { forwardRef } from 'react';
import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from '@/lib/utils';
import { Loader2 } from 'lucide-react';

const buttonVariants = cva(
  'inline-flex items-center justify-center rounded-lg font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none',
  {
    variants: {
      variant: {
        primary: 'bg-primary text-white hover:bg-primary/90',
        secondary: 'border border-gray-300 bg-white hover:bg-gray-50',
        ghost: 'hover:bg-gray-100',
        danger: 'bg-red-500 text-white hover:bg-red-600',
      },
      size: {
        sm: 'h-8 px-3 text-sm',
        md: 'h-10 px-4',
        lg: 'h-12 px-6 text-lg',
      },
    },
    defaultVariants: {
      variant: 'primary',
      size: 'md',
    },
  }
);

interface ButtonProps
  extends React.ButtonHTMLAttributes<HTMLButtonElement>,
    VariantProps<typeof buttonVariants> {
  isLoading?: boolean;
}

export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
  ({ className, variant, size, isLoading, children, ...props }, ref) => {
    return (
      <button
        ref={ref}
        className={cn(buttonVariants({ variant, size }), className)}
        disabled={isLoading || props.disabled}
        {...props}
      >
        {isLoading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
        {children}
      </button>
    );
  }
);

5.2 Input Component

// components/ui/input.tsx
import { forwardRef } from 'react';
import { cn } from '@/lib/utils';

interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
  error?: string;
}

export const Input = forwardRef<HTMLInputElement, InputProps>(
  ({ className, error, ...props }, ref) => {
    return (
      <div className="space-y-1">
        <input
          ref={ref}
          className={cn(
            'w-full h-11 px-4 rounded-lg border bg-white transition-colors',
            'focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary',
            error
              ? 'border-red-500 focus:ring-red-500/20 focus:border-red-500'
              : 'border-gray-300',
            className
          )}
          {...props}
        />
        {error && (
          <p className="text-sm text-red-500">{error}</p>
        )}
      </div>
    );
  }
);

6. State Management

// stores/auth-store.ts
import { create } from 'zustand';
import { persist } from 'zustand/middleware';

interface AuthState {
  token: string | null;
  setToken: (token: string) => void;
  clearToken: () => void;
}

export const useAuthStore = create<AuthState>()(
  persist(
    (set) => ({
      token: null,
      setToken: (token) => set({ token }),
      clearToken: () => set({ token: null }),
    }),
    {
      name: 'auth-storage',
    }
  )
);

7. File Output

# Manually written files
src/
β”œβ”€β”€ app/{route}/page.tsx           # Page components
β”œβ”€β”€ app/providers.tsx               # React Query Provider
β”œβ”€β”€ api/custom-instance.ts          # Custom axios instance
β”œβ”€β”€ components/forms/{form}-form.tsx # Form components
β”œβ”€β”€ components/ui/{component}.tsx   # UI components
β”œβ”€β”€ hooks/use-{feature}.ts         # Custom hooks (wrapping generated hooks)
β”œβ”€β”€ stores/{domain}-store.ts        # Zustand stores
└── lib/schemas/{domain}.ts         # Zod form validation schemas

# Auto-generated files (do not modify)
src/api/
β”œβ”€β”€ generated/{tag}/{tag}.ts      # API hooks/clients
└── model/{type}.ts               # TypeScript types

# Configuration files
β”œβ”€β”€ {api-generator-config}          # API client generation config (e.g., orval.config.ts, openapitools.json)
└── docs/openapi.yaml               # OpenAPI spec

Lint Check Required

Warning: After writing/modifying code, you must run the project's configured linter and fix all errors before finishing. Check crew-config.json for lint commands, or fall back to the defaults below.

Required Execution Order

1. Code writing/modification complete
           β”‚
           β–Ό
2. Run lint auto-fix (--fix)
           β”‚
           β”œβ”€β”€ All errors fixed ──▢ Proceed to step 3
           β”‚
           └── Unfixed errors remain ──▢ Fix manually, then re-run step 2
           β”‚
           β–Ό
3. Lint check (confirm 0 errors)
           β”‚
           β–Ό
4. Type check
           β”‚
           β–Ό
5. Build verification (optional, recommended)
           β”‚
           β–Ό
6. Task complete

Lint Commands by Framework

FrameworkLint Auto-FixLint CheckType Check
Next.jsnpx next lint --fixnpx next lintnpx tsc --noEmit
React (Vite)npm run lint -- --fixnpm run lintnpx tsc --noEmit
Vuenpm run lint -- --fixnpm run lintnpx vue-tsc --noEmit
Sveltenpm run lint -- --fixnpm run lintnpx svelte-check

Handling Errors That Cannot Be Auto-Fixed

Errors not fixed by --fix must be fixed manually:

// ❌ Cannot be auto-fixed: using any type
const data: any = fetchData();  // ESLint: Unexpected any

// βœ… Manual fix: specify correct type
interface UserData { id: string; name: string; }
const data: UserData = fetchData();

Completion Criteria

  • Cannot complete task if lint errors remain
  • Cannot complete task if type errors remain
  • Task is complete only when lint and type checks return 0 errors

Development Principles

1. Lint Check Required

  • Always run the project's lint auto-fix after writing code
  • Manually fix errors that cannot be auto-fixed
  • Also verify type errors with the appropriate type checker
  • Task can only be completed with 0 lint/type errors

2. Generated API Client Must Be Used

  • Direct axios/fetch calls prohibited: Use generated API client methods
  • Do not manually define types: Use auto-generated types from the OpenAPI spec
  • Do not modify generated code: Never edit auto-generated files
  • Regenerate when spec changes: Run the project's API generation script when the OpenAPI spec changes

3. Component Separation

  • Separate components by concern
  • Reusable UI components

4. Import Path Conventions

  • Follow the project's configured import path conventions (path aliases, relative paths, etc.)
  • Prefer path aliases (e.g., @/, ~/) when the project has them configured to avoid deep relative imports

5. Type Safety

  • Use auto-generated types from the OpenAPI spec (@/api/model)
  • Use Zod only for form input validation (separate from API types)
  • Keep generated types and Zod schemas in sync

7. Server vs Client State Separation

  • Manage server state with the project's data-fetching library (React Query, Vue Query, SWR, etc.)
  • Manage client state with the project's state management tool (Zustand, Pinia, Svelte stores, etc.)
  • Invalidate server-state caches after mutations

8. Accessibility

  • Semantic HTML
  • ARIA labels
  • Keyboard navigation

9. Responsive Design

  • Mobile-first approach
  • Leverage the project's CSS framework breakpoints

Note: Examples shown throughout this document are for React/Next.js with Tailwind CSS. Adapt to your framework per crew-config.json β†’ frontend.framework.

Framework-Specific References

Consult the reference that matches your project's crew-config.json β†’ frontend.framework:

FrameworkReference
Next.jsreferences/nextjs-patterns.md
React + Vitereferences/react-vite-patterns.md
Vue 3references/vue-patterns.md
Svelte/SvelteKitreferences/svelte-patterns.md
Angularreferences/angular-patterns.md

References

Keep looking

Skills are one crate of 627,801. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.