Loading...
Loading...
Technical development guidelines for Wednesday Solutions projects. Enforces import ordering, complexity limits, naming conventions, TypeScript best practices, and code quality standards for React/Next.js applications.
npx skill4agent add wednesday-solutions/ai-agent-skills wednesday-dev@trivago/prettier-plugin-sort-imports// 1. React (always first)
import React, { useState, useEffect } from 'react'
// 2. Next.js imports
import { useRouter } from 'next/navigation'
import Image from 'next/image'
// 3. State management (Recoil, Redux)
import { useSelector } from 'react-redux'
// 4. UI libraries (MUI, Radix)
import { Button } from '@mui/material'
// 5. Path alias imports (@/)
import { useAuth } from '@/lib/hooks/useAuth'
import { API_ROUTES } from '@/lib/constants'
// 6. External packages
import { z } from 'zod'
import { motion } from 'framer-motion'
// 7. Internal components
import { Header } from 'components/Header'
// 8. Relative imports (sibling/parent)
import { utils } from './utils'
import { types } from '../types'import type { User } from '@/types'import * as| Complexity | Action Required |
|---|---|
| 1-4 | Good - easy to test |
| 5-7 | Acceptable - consider simplifying |
| 8 | At limit - refactor if adding logic |
| 9+ | Must refactor before merging |
// BAD: High complexity
function getDiscount(user: User, items: Item[]) {
let discount = 0
if (user.isPremium) {
if (items.length > 10) {
discount = 20
} else if (items.length > 5) {
discount = 10
} else {
discount = 5
}
} else {
if (items.length > 10) {
discount = 10
} else if (items.length > 5) {
discount = 5
}
}
return discount
}
// GOOD: Low complexity with lookup table
const DISCOUNT_TABLE = {
premium: { large: 20, medium: 10, small: 5 },
regular: { large: 10, medium: 5, small: 0 },
} as const
function getCartSize(count: number): 'large' | 'medium' | 'small' {
if (count > 10) return 'large'
if (count > 5) return 'medium'
return 'small'
}
function getDiscount(user: User, items: Item[]) {
const tier = user.isPremium ? 'premium' : 'regular'
const size = getCartSize(items.length)
return DISCOUNT_TABLE[tier][size]
}// Components
function UserProfile() { }
function PaymentCard() { }
// Types & Interfaces
interface UserProfileProps { }
type PaymentMethod = 'card' | 'bank'
// Classes
class AuthService { }// Functions
function fetchUserData() { }
function calculateTotal() { }
// Variables
const isLoading = true
const userProfile = { }
// Hooks
function useAuth() { }
function useLocalStorage() { }// Constants
const API_BASE_URL = 'https://api.example.com'
const MAX_RETRY_ATTEMPTS = 3
// Enums
enum UserRole {
ADMIN = 'ADMIN',
USER = 'USER',
GUEST = 'GUEST',
}ishasshouldcanwill// Good
const isAuthenticated = true
const hasPermission = false
const shouldRefetch = true
const canEdit = user.role === 'ADMIN'
// Bad
const authenticated = true
const permission = false
const refetch = true| Type | Convention | Example |
|---|---|---|
| Component files | PascalCase | |
| Hook files | camelCase with | |
| Utility files | camelCase | |
| Constant files | camelCase | |
| Type files | camelCase | |
| Test files | Match source + | |
| Folders | camelCase | |
// Bad - unclear intent
const d = new Date()
const arr = users.filter(u => u.a)
function proc(x: number) { }
// Good - clear intent
const currentDate = new Date()
const activeUsers = users.filter(user => user.isActive)
function processPayment(amount: number) { }tsconfig.jsonanyunknown// Use discriminated unions
type AsyncState<T> =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'success'; data: T }
| { status: 'error'; error: Error }
// Narrow types properly
function processValue(value: unknown) {
if (typeof value === 'string') {
return value.toUpperCase()
}
if (typeof value === 'number') {
return value.toFixed(2)
}
throw new Error('Unsupported type')
}import { useState } from 'react'
import type { FC, ReactNode } from 'react'
import { fetchUser } from '@/lib/api'
import type { User, UserRole } from '@/types'interface UserCardProps {
userId: string
onSelect?: (user: User) => void
}
export function UserCard({ userId, onSelect }: UserCardProps) {
// 1. Hooks
const [isExpanded, setIsExpanded] = useState(false)
const { data: user, isLoading } = useUser(userId)
// 2. Derived state
const displayName = user ? `${user.firstName} ${user.lastName}` : 'Unknown'
// 3. Event handlers
const handleClick = () => {
setIsExpanded(!isExpanded)
onSelect?.(user)
}
// 4. Early returns for loading/error states
if (isLoading) return <Skeleton />
if (!user) return null
// 5. Main render
return (
<Card onClick={handleClick}>
<CardTitle>{displayName}</CardTitle>
{isExpanded && <CardContent>{user.bio}</CardContent>}
</Card>
)
}'use client'
import { useState } from 'react'
// ... client-side component// Forbidden
console.log('debug:', data)
console.error('Error:', err)
// Use structured logging or remove before commit// Bad
if (retryCount > 3) { }
if (status === 'active') { }
// Good
const MAX_RETRIES = 3
const STATUS_ACTIVE = 'active'
if (retryCount > MAX_RETRIES) { }
if (status === STATUS_ACTIVE) { }eslint-plugin-unused-imports_ComponentName.test.tsximport { render, screen, fireEvent } from '@testing-library/react'
import { UserCard } from './UserCard'
describe('UserCard', () => {
it('displays user name correctly', () => {
render(<UserCard userId="123" />)
expect(screen.getByText('John Doe')).toBeInTheDocument()
})
it('calls onSelect when clicked', () => {
const onSelect = jest.fn()
render(<UserCard userId="123" onSelect={onSelect} />)
fireEvent.click(screen.getByRole('button'))
expect(onSelect).toHaveBeenCalledWith(expect.objectContaining({ id: '123' }))
})
})feature.spec.tsnext/dynamicuseMemoReact.memonext/image