Skip to main content

Adding a New Feature

This guide describes the end-to-end process for implementing a complete feature in Elftia. Building on Adding IPC Routes, it covers the full lifecycle including planning, testing, and documentation updates.

Overview: Feature Development Flow

Plan → Backend Implementation → Frontend Implementation → Quality Check → Testing → Documentation Update
flowchart LR
A[Planning] --> B[Backend]
B --> C[Frontend]
C --> D[Quality Check]
D --> E[Testing]
E --> F[Docs Update]
F --> G[Submit PR]

Phase 1: Planning

Before writing any code, answer the following questions clearly:

Requirements Analysis Checklist

QuestionPurpose
What shared types are needed?Determines type files under @shared/contracts/
Does it involve database table operations?Determines whether a new migration is needed
What Service logic is needed?Determines backend modules
What IPC channels are needed?Determines the Router and Preload API
What UI components are needed?Determines the frontend file structure
Does it add a new routed page?Determines whether lazy loading and route registration are needed
Is it user-visible?Determines whether the knowledge base docs need updating

File Planning Template

Using a "Notes" feature as an example:

packages/
├── desktop/app/
│ ├── shared/contracts/
│ │ └── note-types.ts # Shared types
│ └── main/
│ ├── services/content/workspace/notes/
│ │ ├── NoteFileService.ts # File CRUD service
│ │ └── NoteWatcher.ts # File watcher service
│ └── services/routers/
│ └── NoteRouter.ts # IPC Router

├── renderer/src/
│ ├── features/notes/
│ │ ├── hooks/
│ │ │ └── useNotes.ts # Data fetching Hook
│ │ └── components/
│ │ ├── NotesList.tsx # List component
│ │ ├── NoteEditor.tsx # Editor component
│ │ └── index.ts # Barrel export
│ └── locales/
│ ├── en/notes.json
│ ├── zh/notes.json
│ └── ja/notes.json

Phase 2: Backend Implementation

Create backend code in the following order:

2.1 Shared Types

{/* packages/desktop/app/shared/contracts/note-types.ts */}

export interface Note {
id: string;
title: string;
content: string;
tags: string[];
createdAt: string;
updatedAt: string;
}

export interface CreateNoteInput {
title: string;
content?: string;
tags?: string[];
}

2.2 Service

Services encapsulate all business logic. Follow the single-responsibility principle — file read/write and file watching should be two separate Services.

services/content/workspace/notes/
├── NoteFileService.ts # File CRUD operations
└── NoteWatcher.ts # File system change watching

2.3 Router

Register IPC handlers using the secureHandle pattern. All parameters must pass Zod validation.

See Adding IPC Routes for detailed steps.

2.4 Registration + Preload

  1. Create Service and Router instances in registerAllRouters() inside routers/index.ts
  2. Call router.register()
  3. Expose methods in the api object in preload/index.ts

Phase 3: Frontend Implementation

3.1 Create the Hook

Encapsulate data fetching and state management logic in a custom Hook:

{/* packages/renderer/src/features/notes/hooks/useNotes.ts */}

export function useNotes() {
const [notes, setNotes] = useState<Note[]>([]);
const [loading, setLoading] = useState(true);

// Data fetching, CRUD operations...
return { notes, loading, create, update, remove };
}

3.2 Create Components

Components are responsible for UI rendering only; delegate business logic to Hooks:

components/notes/
├── index.ts # Barrel export
├── NotesList.tsx # List view (< 400 lines)
├── NoteEditor.tsx # Editor (< 400 lines)
└── NoteCard.tsx # Single note card

Component standards:

  • Use semantic tokens (bg-surface-1, text-foreground), not hardcoded colors
  • Use project UI components (Button, Input, Select), not native HTML controls
  • Use useTranslation() for copy internationalization

3.3 Route Registration (for new pages)

If the feature needs a standalone page:

{/* packages/renderer/src/app/lazy/pages.tsx */}

export const LazyNotesPage = lazy(() => import('../../features/notes/NotesPage'));
export const SuspenseNotesPage = withSuspense(LazyNotesPage, PageSkeleton);
{/* packages/renderer/src/app/App.tsx — old import paths remain valid via the lazy/index.ts barrel re-export */}

import { SuspenseNotesPage as NotesPage } from './lazy';

<Route path="/notes" element={<NotesPage />} />

For adding a new workspace type (not a new page), follow a different path: edit features/chat/components/content/workspaces/registry.ts, add a definitions/<Name>Workspace.tsx, and add the corresponding lazy import in app/lazy/workspaces.tsx. See the "Adding a New Workspace Type" section in packages/renderer/CLAUDE.md.

3.4 i18n

Create translation files for all three languages and register the namespace in i18n/index.ts:

locales/
├── en/notes.json
├── zh/notes.json
└── ja/notes.json

Phase 4: Quality Check

ESLint

# Check modified files
npx eslint packages/renderer/src/features/notes/components/ packages/renderer/src/features/notes/hooks/

# Auto-fix import ordering, etc.
npx eslint packages/renderer/src/features/notes/components/ --fix

File Size Limits

File TypeRecommendedWarning LineHard Limit
React component400 lines600 lines800 lines
Custom Hook300 lines400 lines600 lines
Utility function150 lines200 lines300 lines
Type definition100 lines150 lines200 lines
Service300 lines400 lines600 lines

Files that exceed the warning line must be split within the current task — do not defer.

TypeScript

npm run typecheck

Phase 5: Testing

Test Commands

CommandDescription
npm run testFull Vitest test run
npm run test -- --filter notesFilter tests by keyword

Manual Testing Checklist

  • The happy path of the feature works correctly
  • Error cases show correct messages (network errors, database errors, etc.)
  • UI displays correctly in dark and light mode
  • UI displays correctly with wallpaper transparency enabled
  • Layout does not break when the window is shrunk to minimum width

Phase 6: Documentation Updates

Required Documentation Updates

ConditionDocumentation to Update
Added a Service / component / Hook / Context.claude/skills/architecture-index/SKILL.md
Added a user-visible page featuredocs/elfi-kb/ knowledge base docs
Added a page routedocs/elfi-kb/INDEX.md route map
Changed an IPC interfaceThis doc site's IPC Channel List

Common Pattern Reference

Pattern 1: CRUD Service

Suitable for entity management features (notes, favorites, templates, etc.).

Shared types → Service(CRUD) → Router(Zod validation) → Preload → Hook(useState+CRUD) → List + edit components

Pattern 2: Streaming Events

Suitable for features that require real-time push (chat, generation tasks, etc.).

Backend Service emits event → Main process webContents.send(channel, data) → Preload onXxx listener → Hook subscribe/unsubscribe
{/* Preload side — event subscription pattern */}

onEvent: (cb: (data: any) => void) => {
const channel = 'myFeature:event';
const handler = (_event: IpcRendererEvent, data: any) => cb(data);
ipcRenderer.on(channel, handler);
return () => ipcRenderer.removeListener(channel, handler);
},
{/* Hook side — subscription management */}

useEffect(() => {
const unsubscribe = window.api.myFeature.onEvent((data) => {
setMessages((prev) => [...prev, data]);
});
return unsubscribe;
}, []);

Pattern 3: File Operations

Suitable for features that need to read or write user files (project management, exports, etc.).

Frontend passes file path/config → Backend Service operates on the file system → Returns result URL or content

Key principle: all file system operations are completed on the backend. The frontend only passes paths and parameters; it does not operate on files directly.


Complete Checklist

Backend

  • Shared types defined in @shared/contracts/
  • Service encapsulates all business logic
  • Router uses secureHandle + Zod validation
  • Router registered in registerAllRouters()
  • Preload API method signatures are correct
  • DesktopApi type interface is updated

Frontend

  • Custom Hook encapsulates data fetching logic
  • Components use semantic tokens and project UI components
  • New pages use React.lazy lazy loading
  • i18n files created for all three languages

Quality

  • npm run lint passes with no errors
  • npm run typecheck passes
  • File sizes are within limits
  • Dark/light mode tests pass
  • Wallpaper transparency mode tests pass

Documentation

  • architecture-index Skill updated
  • elfi-kb knowledge base updated (if user-visible feature added)
  • PR description clearly explains the changes