렌더러 프로세스 설계
렌더러 프로세스는 모든 UI 렌더링과 사용자 상호작용을 처리하며, React 18 + TypeScript로 구축되어 있습니다. 진입 파일은 packages/renderer/src/app/App.tsx입니다.
Context Provider 계층
App 컴포넌트는 엄격한 Provider 중첩 순서를 정의합니다. 바깥쪽 Provider는 안쪽 Provider와 컴포넌트에서 접근할 수 있습니다.
graph TB
LP[LocaleProvider — i18n internationalization]
TP[ThemeProvider — theme/wallpaper]
CDP[ConfirmDialogProvider — global confirm dialog]
AP[AuthProvider — auth state]
SDP[SettingsDataProvider — settings data]
EP[ElfiProvider — Elfi assistant]
WSP[WebSearchProvider — search]
ChatDP[ChatDataProvider — chat data cache]
CBP[ChatBackendProvider — WebSocket backend]
UCP[UnifiedChatProvider — unified chat API]
HPP[HtmlPreviewProvider — HTML preview]
DPP[DevPreviewProvider — dev preview]
PR[ProtectedRoute — route guard]
HR[HashRouter — frontend routing]
CTP[ChatTabsProvider — multi-tab management]
AR[AppRoutes — route definition]
AC[AppContent — main content]
LP --> TP --> CDP --> AP --> SDP --> EP --> WSP --> ChatDP --> CBP --> UCP --> HPP --> DPP --> PR --> HR --> CTP --> AR --> AC
// Actual nesting structure in App.tsx
function App() {
return (
<LocaleProvider>
<ThemeProvider>
<ConfirmDialogProvider>
<AuthProvider>
<SettingsDataProvider>
<ElfiProvider>
<WebSearchProvider>
<ChatDataProvider>
<ChatBackendProvider>
<UnifiedChatProvider>
<HtmlPreviewProvider>
<DevPreviewProvider>
<ProtectedRoute>
<HashRouter>
<ChatTabsProvider>
<AppRoutes>
<AppContent />
</AppRoutes>
</ChatTabsProvider>
</HashRouter>
</ProtectedRoute>
</DevPreviewProvider>
</HtmlPreviewProvider>
</UnifiedChatProvider>
</ChatBackendProvider>
</ChatDataProvider>
</WebSearchProvider>
</ElfiProvider>
</SettingsDataProvider>
</AuthProvider>
</ConfirmDialogProvider>
</ThemeProvider>
</LocaleProvider>
);
}
Zustand Store
고빈도로 업데이트되는 채팅 상태는 Context로 인한 불필요한 리렌더링을 피하기 위해 Zustand를 사용합니다.
chatStore
정밀한 구독을 위해 subscribeWithSelector 미들웨어를 사용하는 핵심 채팅 상태 저장소입니다.
// Simplified chatStore state interface
interface ChatState {
// Sessions
sessions: Session[];
sessionsLoading: boolean;
sessionsError: Error | null;
// Assistants
assistants: ChatAssistant[];
assistantsLoading: boolean;
// Providers
providers: LLMProvider[];
providersLoading: boolean;
// Message cache (indexed by sessionId)
messageCache: Map<string, Message[]>;
// Streaming state
streamingState: StreamingState;
// Branch info
branchInfo: Map<string, BranchInfo>;
// Actions
fetchSessions(): Promise<void>;
fetchAssistants(): Promise<void>;
fetchProviders(): Promise<void>;
loadMessages(sessionId: string): Promise<void>;
setStreamingState(state: Partial<StreamingState>): void;
switchBranch(messageId: string, index: number): void;
}
// Use selector for precise subscription, avoid irrelevant re-renders
const sessions = useChatStore(state => state.sessions);
const isStreaming = useChatStore(state => state.streamingState.isStreaming);
| Store | 파일 | 책임 |
|---|---|---|
chatStore | shared/state/chatStore.ts | 세션, 메시지, 스트리밍 상태, 브랜치 |
settingsStore | shared/state/settingsStore.ts | 설정 상태 |
useChatStoreSync | shared/state/useChatStoreSync.ts | ChatDataContext → chatStore 동기화 |
React Context 세부 정보
23개 이상의 Context가 있으며, 각각 독립적인 책임을 가집니다.
| Context | 파일 | 책임 | 핵심 상태/메서드 |
|---|---|---|---|
LocaleContext | shared/state/LocaleContext.tsx | i18n(en/zh/ja) | useTranslation(), setLocale() |
themeStore | shared/state/themeStore.ts(호스트 app/ThemeHost.tsx) | 테마 모드, 배경화면, 사용자 지정 CSS | mode, resolvedMode, userTheme, customCss |
ConfirmDialogHost | shared/state/ConfirmDialogHost.tsx | 전역 확인 대화상자 | confirm(options) |
authStore | shared/state/authStore.ts | 인증 상태 | user, isAuthenticated, login(), logout() |
SettingsDataHost | app/SettingsDataHost.tsx | 설정 읽기/쓰기 | settings, updateSetting() |
ElfiContext | features/elfi/state/ElfiContext.tsx | Elfi 스마트 어시스턴트 | isOpen, messages, sendMessage() |
webSearchStore | shared/state/webSearchStore.ts(호스트 app/WebSearchHost.tsx) | 웹 검색 | search(), results |
chatStore | shared/state/chatStore.ts | 채팅 데이터 캐시 + 통합 채팅 API(이전 ChatDataContext / UnifiedChatContext가 여기로 마이그레이션됨) | sessions, messages, sendMessage(), regenerate(), editMessage() |
htmlPreviewStore | shared/state/htmlPreviewStore.ts(호스트 app/HtmlPreviewHost.tsx) | HTML 콘텐츠 미리보기 | showPreview(), previewContent |
devPreviewStore | shared/state/devPreviewStore.ts(호스트 app/DevPreviewHost.tsx) | Dev 모드 미리보기 | isDevMode, devData |
ChatTabsContext | features/chat/state/ChatTabsContext.tsx | 다중 탭 채팅 관리 | tabs, activeTabId, createTab(), closeTab() |
AgentContext | features/agents/state/AgentContext.tsx | Agent 상태 | agents, selectedAgent |
subagentStore | shared/state/subagentStore.ts(호스트 shared/state/SubagentHost.tsx) | Subagent 상태 | subagents, status |
MessageSelectionContext | features/chat/state/MessageSelectionContext.tsx | 메시지 다중 선택 | selectedIds, toggleSelect() |
sessionOrganizerStore | shared/state/sessionOrganizerStore.ts(호스트 shared/state/sessionOrganizerStore/SessionOrganizerHost.tsx) | 세션 정리/분류 | folders, moveSession() |
worksStore | shared/state/worksStore.ts | Works 관리 | works, createWork() |
musicWorksStore | shared/state/musicWorksStore.ts | 음악 Works | tracks, playTrack() |
musicTemplateStore | shared/state/musicTemplateStore.ts | 음악 템플릿 | templates |
VideoWorksContext | features/media/state/VideoWorksContext.tsx | 비디오 Works | videos |
videoTemplateStore | shared/state/videoTemplateStore.ts | 비디오 템플릿 | templates |
TemplateContext | features/marketplace/state/TemplateContext.tsx | 이미지 템플릿 | templates |
useWorldInfoHighlight | shared/hooks/characters/useWorldInfoHighlight.ts | WI 키워드 하이라이트 | highlightedKeywords |
사용자 지정 Hooks
60개 이상의 사용자 지정 Hooks가 기능 도메인별로 구성되어 있습니다.
채팅 관련
| Hook | 파일 | 목적 |
|---|---|---|
useAppState | shared/hooks/useAppState.ts | 뷰 플래그, 활성 탭, 사용자 기본 설정을 중앙에서 관리 |
useRouteSync | shared/hooks/useRouteSync.ts | URL 경로를 뷰 상태와 동기화 |
useSessionProtection | features/chat/hooks/useSessionProtection.ts | 활성 세션 보호(새로고침으로 인한 삭제 방지) |
useDraft | features/chat/hooks/useDraft.ts | 입력 초안 저장/복원 |
useAutoUpdate | shared/hooks/useAutoUpdate.ts | 앱 자동 업데이트 |
useAudioRecorder | features/media/hooks/useAudioRecorder.ts | 오디오 녹음 |
기능 Hook 디렉터리
| 디렉터리 | 목적 | 대표 Hooks |
|---|---|---|
features/chat/hooks/ | 채팅 작업 | useMessageStream, useBranchNavigation |
features/marketplace/hooks/channel/ | Channel 플러그인 | useChannelPlugins |
shared/hooks/characters/character/ | 캐릭터 카드 | useCharacterCards |
shared/hooks/characters/worldinfo/ | 세계 정보 | useWorldInfo |
shared/hooks/characters/ | WI 하이라이트 / 태그 | useWorldInfoHighlight, useTagsData |
features/characters/hooks/sprites/ | 표정 스프라이트 | useSprites |
features/settings/components/tabs/tools-tab/hooks/ | MCP 서버 | useMcpManagement |
features/kb/hooks/ | 노트 시스템 | useNotesData |
features/todo/hooks/ | Todos | useTodoData |
features/tasks/hooks/ | 태스크 관리 | useTasksData |
features/cron/hooks/ | 예약된 태스크 | useCronJobs |
features/marketplace/hooks/skills/ | Skill 관리 | useSkillHub |
컴포넌트 아키텍처
레이아웃 구조
graph TB
Root["div.fixed.inset-0.flex.bg-background"]
WS[WorkspaceShell]
WR[WorkspaceRail — left navigation rail]
TB[TitleBar — tab bar + window control]
MC[Main Content — routed content area]
Root --> WS
WS --> WR
WS --> TB
WS --> MC
MC --> ChatPage
MC --> AgentPage
MC --> SettingsPage
MC --> RoleplayPage
MC --> NotesPage
MC --> CoworkPage
- WorkspaceRail: 왼쪽 고정 내비게이션 레일로, Home, Agents, Settings 진입점을 포함합니다.
- WorkspaceShell: 메인 레이아웃 컨테이너로, 레일 + 탭 바 + 콘텐츠 영역을 관리합니다.
- TitleBar: 다중 탭 바 + 창 제어 버튼(최대화/최소화/닫기)입니다.
- Main Content: 라우트에 따라 페이지 컴포넌트를 동적으로 렌더링합니다.
Workspace 확장(Workspace Registry)
Chat/Agent 페이지 사이의 "workspace 패널"(파일 트리 오른쪽의 editor / git diff / HTML preview / canvas / design studio 영역)은 WorkspaceDefinition registry를 통해 확장됩니다. 호스트 Shell인 WorkspaceArea.tsx는 레이아웃 + registry 디스패치만 수행하며, activeView === 'X' 같은 비즈니스 분기는 포함하지 않습니다.
graph TB
WA["WorkspaceArea<br/>(layout + dispatch)"]
Reg["registry.ts<br/>WORKSPACE_REGISTRY[]"]
Slot["WorkspaceToolbarProvider<br/>(slot context)"]
Aff["host-affordances<br/>(cross-view coordination)"]
Defs["definitions/"]
WA -->|findActiveWorkspace| Reg
WA -->|wraps| Slot
WA -->|consumes| Aff
Reg -->|imports| Defs
Defs --> EW[EditorWorkspace]
Defs --> GW[GitWorkspace]
Defs --> HW[HtmlPreviewWorkspace]
Defs --> AW[A2UIWorkspace]
Defs --> DW[DevPreviewWorkspace]
Defs --> DS[DesignStudioWorkspace]
Defs --> CW[CanvasWorkspace]
각 WorkspaceDefinition은 다음을 자체 관리합니다.
- 자체
view식별자 +isAvailable(ctx)조건자 - 자체 뷰 모드 상태(split/code/preview 등 내부 useState — 호스트로 누출되지 않음)
useWorkspaceToolbar(config)로 슬롯에 툴바 구성 게시. 호스트의<WorkspaceToolbar>는useWorkspaceToolbarConfig()로 읽음- 선택적
onClose(ctx, cb)hook(닫기 버튼으로 트리거되고 React 트리 밖에서 실행되므로 close-fn 채널은WorkspaceCallbacks를 사용)
새 workspace 유형 추가 = 2개 파일 수정: registry.ts 배열에 항목 추가 + definitions/<Name>Workspace.tsx 생성. WorkspaceArea.tsx는 변경할 필요가 없습니다. 자세한 절차는 packages/renderer/CLAUDE.md의 "🧩 Add New Workspace Type" 섹션, 계약 사양은 openspec/specs/workspace-registry/spec.md에 있습니다.
크로스 뷰 조정(예: editor의 "Preview" 버튼이 htmlPreview 뷰로 이동, HTML preview 최대화 시 전체 workspace 패널 숨김)은 host-affordances.ts에 중앙화되어 있습니다. 이곳만 activeView === 'X' 판단이 허용되는 위치입니다.
| 파일 | 책임 |
|---|---|
features/chat/components/content/WorkspaceArea.tsx | 호스트 Shell(레이아웃 + 슬롯 리더 + registry 디스패치, 비즈니스 분기 없음) |
features/chat/components/content/workspaces/types.ts | WorkspaceDefinition / WorkspaceHostContext / WorkspaceCallbacks 계약 |
features/chat/components/content/workspaces/registry.ts | WORKSPACE_REGISTRY 단일 등록 지점 + findActiveWorkspace 쿼리 |
features/chat/components/content/workspaces/host-affordances.ts | 크로스 뷰 조정(크로스 뷰 토글 + 최대화 숨김) |
features/chat/components/content/workspaces/workspace-toolbar-slot{,-internals,-hooks}.{tsx,ts} | 툴바 슬롯 3종(Provider / contexts / hooks) |
features/chat/components/content/workspaces/definitions/<Name>Workspace.tsx | 7개 workspace 정의(editor / git / htmlPreview / a2ui / devPreview / designStudio / canvas) |
features/chat/components/content/workspaces/inline-file-viewers.tsx | InlineMarkdownViewer + InlineBinaryViewer(image/audio/video/pdf + "미리보기 불가" 폴백) |
features/chat/components/content/workspaces/inline-file-viewers-types.ts | getBinaryFileType / isMarkdownFile / isNonTextFile 파일 유형 감지 |
features/chat/components/content/unified-page/hooks/useWorkspaceState.ts | Workspace 상태 hook(.html → openHtmlPreview() 라우팅 + handleFileSelect의 바이너리 파일 라우팅) |
라우팅 시스템
HashRouter(Electron은 BrowserRouter를 지원하지 않음)를 사용하고, useRouteSync Hook으로 URL을 뷰 상태와 동기화합니다.
// Sync route to view state
function useRouteSync(pathname: string, state: AppState) {
useEffect(() => {
if (pathname.startsWith('/chat')) {
state.setIsChatView(true);
state.setIsHomeView(false);
} else if (pathname.startsWith('/agents')) {
state.setIsAgentView(true);
} else if (pathname.startsWith('/settings')) {
state.setIsSettingsView(true);
}
}, [pathname]);
}
i18n 국제화
useTranslation() Hook을 사용해 3개 언어를 지원합니다.
| 언어 | 디렉터리 | 파일 |
|---|---|---|
| English | locales/en/ | 15개 이상의 JSON 파일 |
| Chinese (Simplified) | locales/zh/ | 15개 이상의 JSON 파일 |
| Japanese | locales/ja/ | 15개 이상의 JSON 파일 |
번역 파일은 common.json, chat.json, settings.json, characters.json, elfi.json 등 기능 도메인별로 분할됩니다.
AppState Hook
useAppState는 전역 UI 상태를 중앙에서 관리합니다.
// Simplified AppState interface
interface AppState {
// View flags
isHomeView: boolean;
isChatView: boolean;
isAgentView: boolean;
isSettingsView: boolean;
// Active session
selectedSession: string | null;
activeSessions: Set<string>;
processingSessions: Set<string>;
// Tabs
activeTab: string;
settingsInitialTab: string;
// User preferences
autoExpandTools: boolean;
showRawParameters: boolean;
showThinking: boolean;
autoScrollToBottom: boolean;
sendByCtrlEnter: boolean;
// Modals
showVersionModal: boolean;
}
빌드 구성
| 구성 | 값 |
|---|---|
| Build tool | Vite 7.0 |
| Styling | Tailwind CSS 3.4 + CSS 변수(시맨틱 토큰) |
| Code splitting | 라우트 수준 지연 로딩(lazy/ 디렉터리, 목적별 6개 파일 + 1개 barrel) |
| Tree shaking | Named imports |
| Main bundle limit | 500 KB |
관련 파일
| 파일 | 설명 |
|---|---|
packages/renderer/src/app/App.tsx | App 진입점, Provider 계층 및 레이아웃 |
packages/renderer/src/shared/state/chatStore.ts | Zustand 채팅 상태 Store |
packages/renderer/src/shared/state/settingsStore.ts | Zustand 설정 상태 Store |
packages/renderer/src/shared/state/chatStore.ts | 통합 채팅 API(이전 UnifiedChatContext가 chatStore로 마이그레이션됨) |
packages/renderer/src/shared/state/themeStore.ts | 테마 Store(이전 ThemeContext) |
packages/renderer/src/shared/hooks/useAppState.ts | 중앙화된 UI 상태 관리 |
packages/renderer/src/shared/hooks/useRouteSync.ts | 라우트 동기화 |
packages/renderer/src/app/layout/WorkspaceShell.tsx | 메인 레이아웃 컨테이너 |
packages/renderer/src/app/layout/WorkspaceRail.tsx | 왼쪽 내비게이션 레일 |
packages/renderer/src/app/title-bar/ | 탭 바 컴포넌트 |
packages/renderer/src/app/lazy/ | 지연 로드 컴포넌트 디렉터리(pages.tsx / workspaces.tsx / inline.tsx / shell.tsx / skeletons.tsx / with-suspense.tsx, barrel은 lazy/index.ts) |
packages/renderer/src/features/chat/components/content/WorkspaceArea.tsx | 중앙 workspace 호스트 Shell(레이아웃 + registry 디스패치, 비즈니스 분기 없음) |
packages/renderer/src/features/chat/components/content/workspaces/ | Workspace registry(WorkspaceDefinition 계약 + 7개 정의 파일 + 툴바 슬롯 + host-affordances) |
packages/renderer/src/app/railItems.ts | 내비게이션 레일 구성 |
packages/renderer/src/app/mainContentRouter.tsx | 메인 콘텐츠 라우터 파싱 |
packages/renderer/CLAUDE.md | 프론트엔드 UI 개발 표준 |