ワンクリックで
coding-standards
TypeScript + React coding standards, architecture patterns, naming conventions, ESLint configuration
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
TypeScript + React coding standards, architecture patterns, naming conventions, ESLint configuration
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
SOC 職業分類に基づく
Use when designing, authoring, or reviewing a closed agent loop (test→fix→retest, audit→fix→re-audit, generate→verify→correct) in this marketplace — the minimum-bar checklist, the ground-truth oracle taxonomy, and the anti-patterns, anchored to /qa:loop as the reference implementation.
Test report format with QA-XXX issue IDs compatible with code-review plugin. Defines report structure, severity levels, issue format, and detailed results.
Bun package management, lockfile policy, workspaces, CI integration, and Bun-native tooling
Backend testing patterns — API request construction, response verification, database state checks, error handling testing, and adaptive tool detection.
Frontend testing patterns using Playwright MCP — navigation, interaction, assertions, screenshots on failure, and common UI testing scenarios.
Test plan structure, naming conventions, edge case generation rules, and file saving conventions for QA test plans.
| name | coding-standards |
| description | TypeScript + React coding standards, architecture patterns, naming conventions, ESLint configuration |
Foundational rules for TypeScript + React development. Enforced in all code:
any — use unknown + type guards insteadas type assertions except as const! non-null assertion — use optional chaining ?. and nullish coalescing ??enum — use as const objects + derived union types@ts-ignore — use @ts-expect-error with comment if absolutely unavoidableinterface for object shapes, type for unions/intersectionsI prefix on interfaces (User, not IUser)import type { } for type-only importsexport * or barrel exports >5 re-exportstsconfig.json MUST include:
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true,
"isolatedModules": true,
"noImplicitAny": true,
"noImplicitThis": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictBindCallApply": true,
"strictPropertyInitialization": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"moduleResolution": "bundler",
"resolveJsonModule": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"paths": {
"@/*": ["./src/*"]
}
}
}
T | null instead of T | undefined unless specifically undefinedprop?: T (equivalent to prop: T | undefined)T | nullExample:
// ❌ BAD: Missing parameter and return types
function processUser(user) {
return user.name.toUpperCase();
}
// ✅ GOOD: All types explicit
function processUser(user: User): string {
return user.name.toUpperCase();
}
// ❌ BAD: Using type assertion
const id = userId as number;
// ✅ GOOD: Type guard or explicit context
function handleUser(userId: string | number): void {
if (typeof userId === 'number') {
// userId is number here
}
}
interface for object shapes (especially component props)type for unions, intersections, tuples, primitivesExample:
// ✅ GOOD: Interface for props
interface ButtonProps {
variant: 'primary' | 'secondary';
disabled?: boolean;
children: React.ReactNode;
}
// ✅ GOOD: Type for union
type Status = 'idle' | 'loading' | 'success' | 'error';
// ✅ GOOD: Type for intersection
type WithTimestamp = { createdAt: Date };
type User = { id: string; name: string } & WithTimestamp;
import *)import type { SomeType } from '...'Example:
// ✅ GOOD: Organized imports
import { useState } from 'react';
import type { ReactNode } from 'react';
import { QueryClient } from '@tanstack/react-query';
import type { User } from '@/types/user';
import { useAuth } from '@/features/auth';
React.FC — use named function with direct props annotationchildren implicitly — ALWAYS explicitly annotate children: ReactNode in propsfireEvent in tests — ALWAYS use userEventgetByText as first choice — prefer screen.getByRolegetByTestId without strong justificationonClick handlers without specific typesany for event handlers — use React-specific typesref.current! — use optional chaining or proper type narrowingundefined — throw in the hook insteadReact.FC<Props>children: React.ReactNode if component accepts childrenProps suffix for interfaces (ButtonProps, CardProps)Example:
// ❌ BAD: Using React.FC
const Button: React.FC<{ label: string; onClick: () => void }> = ({ label, onClick }) => (
<button onClick={onClick}>{label}</button>
);
// ✅ GOOD: Direct props annotation with children
interface ButtonProps {
label: string;
onClick: () => void;
children?: React.ReactNode;
disabled?: boolean;
}
function Button({ label, onClick, children, disabled }: ButtonProps): React.ReactElement {
return (
<button onClick={onClick} disabled={disabled}>
{label} {children}
</button>
);
}
React.MouseEvent<HTMLButtonElement>, React.ChangeEvent<HTMLInputElement>any for event parametersExample:
interface FormProps {
onSubmit: (event: React.FormEvent<HTMLFormElement>) => void;
onChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
}
function Form({ onSubmit, onChange }: FormProps): React.ReactElement {
return (
<form onSubmit={onSubmit}>
<input onChange={onChange} />
</form>
);
}
use prefixExample:
// ❌ BAD: Hook inside condition
function Component({ shouldFetch }: { shouldFetch: boolean }): React.ReactElement {
if (shouldFetch) {
const data = useFetch(); // WRONG!
}
return <div />;
}
// ✅ GOOD: Hook at top level
function Component({ shouldFetch }: { shouldFetch: boolean }): React.ReactElement {
const data = useFetch();
if (!shouldFetch) return <div />;
return <div>{data}</div>;
}
createContext should be null or undefinedExample:
// ❌ BAD: Context without typed hook
const ThemeContext = React.createContext<Theme | undefined>(undefined);
// ✅ GOOD: Context with typed hook
const ThemeContext = React.createContext<Theme | null>(null);
function useTheme(): Theme {
const theme = React.useContext(ThemeContext);
if (theme === null) {
throw new Error('useTheme must be used within ThemeProvider');
}
return theme;
}
interface ThemeProviderProps {
children: React.ReactNode;
}
function ThemeProvider({ children }: ThemeProviderProps): React.ReactElement {
const [theme, setTheme] = React.useState<Theme>(defaultTheme);
return (
<ThemeContext.Provider value={theme}>
{children}
</ThemeContext.Provider>
);
}
src/
├── app/ # Shell: routes, providers, entry point
│ ├── App.tsx # Main route component
│ └── Root.tsx # Root with providers
├── features/ # Self-contained feature modules
│ ├── auth/
│ │ ├── api/ # API calls + TanStack Query hooks
│ │ ├── components/ # Feature-scoped UI
│ │ ├── hooks/ # Feature-scoped logic hooks
│ │ ├── stores/ # Feature-scoped Zustand stores
│ │ ├── types/ # Feature-scoped types
│ │ └── index.ts # Public API (max 5 exports)
│ ├── dashboard/
│ │ ├── api/
│ │ ├── components/
│ │ └── ...
│ └── users/
│ └── ...
├── components/ # Shared UI (Button, Modal, Card)
│ └── ui/ # Primitives (shadcn/ui style)
├── hooks/ # Shared hooks (useDebounce, useMediaQuery)
├── lib/ # Preconfigured libs (axios instance, cn utility)
├── stores/ # Global state stores
├── types/ # Shared TypeScript types
├── utils/ # Pure utility functions
└── test/ # Test setup, custom render, MSW handlers
Direction: shared -> features -> app
shared/ (hooks, utils, components, types, lib, stores)features/ and shared/Cross-feature composition happens ONLY in app/ (root routes/pages).
Example:
// ✅ GOOD: Auth feature imports shared utilities
import { formatDate } from '@/utils/format'; // from shared utils
import { Button } from '@/components/ui/button'; // from shared components
import type { User } from '@/types/user'; // from shared types
// ❌ BAD: Auth feature imports from orders feature
import { useOrders } from '@/features/orders/hooks'; // WRONG!
// ✅ GOOD: App composes features together
import { AuthPage } from '@/features/auth';
import { DashboardPage } from '@/features/dashboard';
Layer 1: API/Service Layer
features/<name>/api/<name>Api.tsfeatures/<name>/api/queries.ts for TanStack QueryLayer 2: Logic/Hook Layer
features/<name>/hooks/Layer 3: UI/Component Layer
features/<name>/components/Example:
// Layer 1: API service (pure function)
// features/users/api/usersApi.ts
export async function fetchUser(id: string): Promise<User> {
const response = await apiClient.get(`/users/${id}`);
return response.data;
}
// Layer 2: Hook (TanStack Query + Zustand)
// features/users/hooks/useUser.ts
export function useUser(id: string): {
user: User | undefined;
isLoading: boolean;
error: Error | null;
} {
const { data: user, isLoading, error } = useQuery({
queryKey: ['users', id],
queryFn: () => fetchUser(id),
});
return { user, isLoading, error };
}
// Layer 3: Component (UI only)
// features/users/components/UserCard.tsx
interface UserCardProps {
userId: string;
}
function UserCard({ userId }: UserCardProps): React.ReactElement {
const { user, isLoading } = useUser(userId);
if (isLoading) return <div>Loading...</div>;
if (!user) return <div>User not found</div>;
return <div>{user.name}</div>;
}
| Element | Convention | Example |
|---|---|---|
| Directories | kebab-case | user-profile/, auth-modal/ |
| Component files | PascalCase.tsx | UserProfile.tsx, LoginForm.tsx |
| Component exports | PascalCase function | export function UserProfile() {} |
| Hook files | camelCase with use prefix | useAuth.ts, useFormData.ts |
| Hook exports | camelCase with use prefix | export function useAuth() |
| Utility functions | camelCase | formatDate.ts, calculateTotal.ts |
| Types/Interfaces | PascalCase | User, ButtonProps, FormData |
| Constants | UPPER_SNAKE_CASE | API_BASE_URL, MAX_RETRIES |
| Const objects | PascalCase + as const | const OrderStatus = {...} as const |
| Test files | .test.tsx for components, .spec.ts for e2e | Button.test.tsx, auth.spec.ts |
Props suffix: ButtonProps, CardProps, ModalProps?: disabled?: booleanon: onClick, onChange, onSubmitExample:
interface UserProfileProps {
userId: string;
onLogout?: () => void;
disabled?: boolean;
}
function UserProfile({ userId, onLogout, disabled }: UserProfileProps): React.ReactElement {
// ...
}
// eslint.config.js
export default [
{
files: ['**/*.{ts,tsx}'],
rules: {
'@typescript-eslint/no-explicit-any': 'error',
'@typescript-eslint/no-non-null-assertion': 'error',
'@typescript-eslint/consistent-type-imports': [
'error',
{ prefer: 'type-imports', fixStyle: 'inline-type-imports' }
],
'react-hooks/rules-of-hooks': 'error',
'react-hooks/exhaustive-deps': 'warn',
'@typescript-eslint/explicit-function-return-types': 'error',
'@typescript-eslint/explicit-module-boundary-types': 'error',
'no-console': ['warn', { allow: ['warn', 'error'] }],
'no-var': 'error',
'prefer-const': 'error',
},
},
];
@typescript-eslint/eslint-plugin with strict-type-checked + stylistic-type-checkedeslint-plugin-react-hookseslint-plugin-import for import orderingAll imports from src/ use @/ instead of relative paths.
tsconfig.json:
{
"compilerOptions": {
"paths": {
"@/*": ["./src/*"]
}
}
}
vite.config.ts:
import react from '@vitejs/plugin-react';
import { defineConfig } from 'vite';
import path from 'path';
export default defineConfig({
plugins: [react()],
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
},
},
});
Always use path aliases:
// ❌ BAD: Relative imports
import { Button } from '../../../../components/ui/button';
// ✅ GOOD: Path aliases
import { Button } from '@/components/ui/button';
Example:
import { useState } from 'react';
import type { ReactNode } from 'react';
import clsx from 'clsx';
import type { User } from '@/types/user';
import { Button } from '@/components/ui/button';
interface UserCardProps {
user: User;
onSelect: (id: string) => void;
}
export function UserCard({ user, onSelect }: UserCardProps): ReactNode {
const [isHovered, setIsHovered] = useState(false);
return (
<div
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
className={clsx('p-4', isHovered && 'bg-gray-100')}
>
<h3>{user.name}</h3>
<Button onClick={() => onSelect(user.id)}>Select</Button>
</div>
);
}
Example:
/**
* Formats a user's display name, handling cases where
* firstName or lastName might be missing.
*/
export function formatUserName(
firstName: string | null,
lastName: string | null,
): string {
// Prefer full name, fall back to firstName, then lastName
if (firstName && lastName) return `${firstName} ${lastName}`;
return firstName ?? lastName ?? 'Unknown User';
}
All code must follow these standards:
any, as, !React.FC