Skip to main content

Code Standards

Elftia uses strict ESLint configuration (typescript-eslint/strict) with a progressive enforcement strategy.


Toolchain

ToolPurposeConfiguration File
ESLintCode quality checks (strict mode)eslint.config.js
PrettierCode formatting.prettierrc
HuskyGit Hooks management.husky/pre-commit
lint-stagedStaged file checkspackage.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

LevelMeaningCommit Behavior
errorMust fixBlocks commit
warnRecommended fixAllows commit
offDisabled-

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

RuleDescriptionFix
@typescript-eslint/no-unused-varsUnused variablesDelete or prefix with _
@typescript-eslint/no-explicit-anyUsing any typeChange to specific type or unknown
@typescript-eslint/consistent-type-importsNon type-only importsChange to import { type Foo }
@typescript-eslint/no-non-null-assertionNon-null assertion !Use optional chaining ?. or type guard

React Rules

RuleDescriptionFix
react-hooks/exhaustive-depsIncomplete hook dependenciesComplete dependencies array
react/jsx-no-leaked-renderLeaked render (count && <X />)Change to count > 0 && <X />
react/self-closing-compEmpty tag not self-closed<Comp></Comp><Comp />
react/jsx-curly-brace-presenceUnnecessary bracesprop={"val"}prop="val"
react/jsx-boolean-valueRedundant boolean valuedisabled={true}disabled
react/hook-use-stateState name not camelCase[Value, setV][value, setV]

Other Rules

RuleDescriptionFix
no-consoleRenderer process uses console.logChange to console.warn/error/info/debug
jsx-a11y/*Accessibility issuesSee 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

OptionValueDescription
semitrueUse semicolons
singleQuotetrueUse single quotes
tabWidth2Indentation width
trailingComma'es5'Trailing commas
printWidth100Line width limit

File Size Limits

Golden rule: A single file should not exceed 600 lines of code.

File TypeRecommended LimitWarning LineHard Limit
React component400 lines600 lines800 lines
Custom Hook300 lines400 lines600 lines
Utility/helper functions150 lines200 lines300 lines
Type definitions100 lines150 lines200 lines
Service/API300 lines400 lines600 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

TypeRuleExample
React componentPascalCase.tsxUserMessage.tsx
Hooksuse + PascalCase.tsuseChatActions.ts
Utility functionscamelCase.tsmessageHelpers.ts
Type definitionscamelCase.ts or types.tschatTypes.ts
ConstantsUPPER_CASE.tsAPI_CONSTANTS.ts
ServicesPascalCase + Service.tsChatService.ts
Directorieskebab-casechat-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:

  1. It exceeds 300 lines of code
  2. It contains 3 or more independent logic blocks
  3. It has reusable parts
  4. Different parts change at different frequencies
  5. Testing becomes difficult

Splitting strategies:

StrategyWhen to UseExample
Split by featureUI blocks are independentToolbar / Message list / Input box → separate components
Split by data flowComplex state logicState / Actions / Side effects → separate Hooks
Split by domainMultiple similar itemsEditTool / 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

  1. Run npm run lint — Ensure no errors
  2. Fix all error-level issues
  3. Fix warn-level issues when possible
  4. Run npm run format — Format code
  5. New files should have 0 warnings
  6. When modifying existing files, fix warnings in that file as well