Code Standards
Elftia uses strict ESLint configuration (typescript-eslint/strict) with a progressive enforcement strategy.
Toolchain
| Tool | Purpose | Configuration File |
|---|---|---|
| ESLint | Code quality checks (strict mode) | eslint.config.js |
| Prettier | Code formatting | .prettierrc |
| Husky | Git Hooks management | .husky/pre-commit |
| lint-staged | Staged file checks | package.json |
Common Commands
# Run ESLint checks
npm run lint:eslint
# Check Prettier formatting
npm run lint:prettier
# Auto-format code
npm run format
# Complete lint check (ESLint + TypeScript)
npm run lint
# Check a single file
npx eslint path/to/file.ts
# Auto-fix a single file
npx eslint path/to/file.ts --fix
Pre-Commit Automatic Checks
When committing code, Husky automatically runs lint-staged on staged .ts/.tsx files with eslint --fix:
- error level: Blocks commit, must be fixed
- warn level: Shows warning, allows commit
- auto-fix: Automatically fixes fixable issues like import sorting
ESLint Rules Quick Reference
Severity Levels
| Level | Meaning | Commit Behavior |
|---|---|---|
error | Must fix | Blocks commit |
warn | Recommended fix | Allows commit |
off | Disabled | - |
Error Level Rules (Block Commit)
// simple-import-sort/imports — Auto-fixable
// Imports must be ordered as specified, eslint --fix will auto-fix
// prefer-const
let value = 1; // Never reassigned, should use const
// no-var
var x = 1; // var usage is prohibited
// eqeqeq
if (a == b) { } // Use == instead of === (except for null checks)
// react-hooks/rules-of-hooks
if (condition) { useState(); } // Hooks can only be called at top level
Warn Level Rules (Allow Commit)
TypeScript Rules
| Rule | Description | Fix |
|---|---|---|
@typescript-eslint/no-unused-vars | Unused variables | Delete or prefix with _ |
@typescript-eslint/no-explicit-any | Using any type | Change to specific type or unknown |
@typescript-eslint/consistent-type-imports | Non type-only imports | Change to import { type Foo } |
@typescript-eslint/no-non-null-assertion | Non-null assertion ! | Use optional chaining ?. or type guard |
React Rules
| Rule | Description | Fix |
|---|---|---|
react-hooks/exhaustive-deps | Incomplete hook dependencies | Complete dependencies array |
react/jsx-no-leaked-render | Leaked render (count && <X />) | Change to count > 0 && <X /> |
react/self-closing-comp | Empty tag not self-closed | <Comp></Comp> → <Comp /> |
react/jsx-curly-brace-presence | Unnecessary braces | prop={"val"} → prop="val" |
react/jsx-boolean-value | Redundant boolean value | disabled={true} → disabled |
react/hook-use-state | State name not camelCase | [Value, setV] → [value, setV] |
Other Rules
| Rule | Description | Fix |
|---|---|---|
no-console | Renderer process uses console.log | Change to console.warn/error/info/debug |
jsx-a11y/* | Accessibility issues | See Design System |
Import Sorting Rules
Imports must be ordered as follows (eslint --fix can auto-fix):
// 1. Node.js built-in modules
import path from 'node:path';
import fs from 'node:fs';
// 2. External packages
import React, { useState } from 'react';
import { useQuery } from '@tanstack/react-query';
// 3. Internal alias @/
import { Button } from '@/components/ui/button';
import { useAppState } from '@/shared/hooks/useAppState';
// 4. @shared/ alias
import type { Message } from '@shared/contracts';
// 5. @main/ alias
import { AppPaths } from '@main/services/infra/paths/paths';
// 6. Parent directory imports
import { utils } from '../utils';
// 7. Same directory imports
import { helper } from './helper';
// 8. Style imports
import './styles.css';
Prettier Configuration
| Option | Value | Description |
|---|---|---|
semi | true | Use semicolons |
singleQuote | true | Use single quotes |
tabWidth | 2 | Indentation width |
trailingComma | 'es5' | Trailing commas |
printWidth | 100 | Line width limit |
File Size Limits
Golden rule: A single file should not exceed 600 lines of code.
| File Type | Recommended Limit | Warning Line | Hard Limit |
|---|---|---|---|
| React component | 400 lines | 600 lines | 800 lines |
| Custom Hook | 300 lines | 400 lines | 600 lines |
| Utility/helper functions | 150 lines | 200 lines | 300 lines |
| Type definitions | 100 lines | 150 lines | 200 lines |
| Service/API | 300 lines | 400 lines | 600 lines |
Consequences:
- Exceeds warning line → Must split in current task, do not defer
- Exceeds hard limit → Stop immediately, refactor first, then continue
Naming Conventions
File Naming
| Type | Rule | Example |
|---|---|---|
| React component | PascalCase.tsx | UserMessage.tsx |
| Hooks | use + PascalCase.ts | useChatActions.ts |
| Utility functions | camelCase.ts | messageHelpers.ts |
| Type definitions | camelCase.ts or types.ts | chatTypes.ts |
| Constants | UPPER_CASE.ts | API_CONSTANTS.ts |
| Services | PascalCase + Service.ts | ChatService.ts |
| Directories | kebab-case | chat-messages/ |
Component Naming
// Good: Clear and descriptive
export function UserMessage() { }
export function EditTool() { }
export function ChatComposer() { }
// Bad: Too generic
export function Message() { }
export function Tool() { }
export function Input() { }
Hook Naming
// Good: use + feature description
export function useChatActions() { }
export function useMessageEffects() { }
// Bad: Does not follow React Hook naming convention
export function chatActions() { }
export function messageHook() { }
Directory Structure Standards
Component Directory
components/
├── ComponentName/
│ ├── index.ts # Unified export
│ ├── ComponentName.tsx # Main component
│ ├── types.ts # Type definitions
│ ├── utils.ts # Utility functions
│ ├── SubComponent.tsx # Sub-component
│ └── __tests__/
│ └── ComponentName.test.tsx
Hooks Directory
hooks/
├── domain/
│ ├── index.ts # Unified export
│ ├── useDomainState.ts # State
│ ├── useDomainActions.ts # Actions
│ └── useDomainEffects.ts # Side effects
Services Directory
services/
├── ServiceName/
│ ├── index.ts # Unified export
│ ├── ServiceName.ts # Main service
│ ├── types.ts # Type definitions
│ └── subdomain/ # Sub-domain
TypeScript Best Practices
Type Imports
// Use type-only imports
import { type SomeType } from './types';
import type { AnotherType } from '@shared/contracts';
Avoid any
// Bad
function handle(data: any) { }
// Good: Use specific types
function handle(data: Message) { }
// Good: Use generics
function handle<T extends BaseMessage>(data: T) { }
// Good: Use unknown + type guard
function handle(data: unknown) {
if (isMessage(data)) { /* data: Message */ }
}
Barrel Export
// components/chat/messages/index.ts
export { MessageItem } from './MessageItem';
export { MessageList } from './MessageList';
export type { MessageItemProps } from './MessageItem';
Code Organization Order
React Component Structure
// 1. Imports
// 2. Type definitions
// 3. Constants
// 4. Helper functions
// 5. Main component
// 5.1 Hooks
// 5.2 Derived state (useMemo)
// 5.3 Event handlers (useCallback)
// 5.4 Effects (useEffect)
// 5.5 Render (return)
// 6. Sub-components (if simple)
Single Responsibility Principle
Each file should have only one reason to change. A component should be split when:
- It exceeds 300 lines of code
- It contains 3 or more independent logic blocks
- It has reusable parts
- Different parts change at different frequencies
- Testing becomes difficult
Splitting strategies:
| Strategy | When to Use | Example |
|---|---|---|
| Split by feature | UI blocks are independent | Toolbar / Message list / Input box → separate components |
| Split by data flow | Complex state logic | State / Actions / Side effects → separate Hooks |
| Split by domain | Multiple similar items | EditTool / WriteTool / BashTool → separate files |
Git Workflow
Commit Convention
Use Conventional Commits format:
feat: New feature description
fix: Bug fix description
refactor: Refactoring description
docs: Documentation update
style: Code formatting changes (no logic changes)
test: Test-related changes
chore: Build tool or auxiliary tool changes
Pre-Commit Checklist
- Run
npm run lint— Ensure no errors - Fix all error-level issues
- Fix warn-level issues when possible
- Run
npm run format— Format code - New files should have 0 warnings
- When modifying existing files, fix warnings in that file as well