Renderer Process Design
The renderer process handles all UI rendering and user interactions, built with React 18 + TypeScript. Entry file is packages/renderer/src/app/App.tsx.
Context Provider Hierarchy
App component defines a strict Provider nesting order. Outer Providers can be accessed by inner Providers and components:
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
High-frequency updated chat state uses Zustand to avoid unnecessary re-renders from Context.
chatStore
Core chat state storage using subscribeWithSelector middleware for precise subscription:
// 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 | File | Responsibility |
|---|---|---|
chatStore | shared/state/chatStore.ts | Sessions, messages, streaming state, branches |
settingsStore | shared/state/settingsStore.ts | Settings state |
useChatStoreSync | shared/state/useChatStoreSync.ts | ChatDataContext → chatStore sync |
React Context Details
23+ Contexts, each with independent responsibility:
| Context | File | Responsibility | Core State/Methods |
|---|---|---|---|
LocaleContext | shared/state/LocaleContext.tsx | i18n (en/zh/ja) | useTranslation(), setLocale() |
themeStore | shared/state/themeStore.ts (host app/ThemeHost.tsx) | Theme mode, wallpaper, custom CSS | mode, resolvedMode, userTheme, customCss |
ConfirmDialogHost | shared/state/ConfirmDialogHost.tsx | Global confirm dialog | confirm(options) |
authStore | shared/state/authStore.ts | Auth state | user, isAuthenticated, login(), logout() |
SettingsDataHost | app/SettingsDataHost.tsx | Settings read/write | settings, updateSetting() |
ElfiContext | features/elfi/state/ElfiContext.tsx | Elfi smart assistant | isOpen, messages, sendMessage() |
webSearchStore | shared/state/webSearchStore.ts (host app/WebSearchHost.tsx) | Web search | search(), results |
chatStore | shared/state/chatStore.ts | Chat data cache + unified chat API (old ChatDataContext / UnifiedChatContext migrated here) | sessions, messages, sendMessage(), regenerate(), editMessage() |
htmlPreviewStore | shared/state/htmlPreviewStore.ts (host app/HtmlPreviewHost.tsx) | HTML content preview | showPreview(), previewContent |
devPreviewStore | shared/state/devPreviewStore.ts (host app/DevPreviewHost.tsx) | Dev mode preview | isDevMode, devData |
ChatTabsContext | features/chat/state/ChatTabsContext.tsx | Multi-tab chat management | tabs, activeTabId, createTab(), closeTab() |
AgentContext | features/agents/state/AgentContext.tsx | Agent state | agents, selectedAgent |
subagentStore | shared/state/subagentStore.ts (host shared/state/SubagentHost.tsx) | Subagent state | subagents, status |
MessageSelectionContext | features/chat/state/MessageSelectionContext.tsx | Message multi-select | selectedIds, toggleSelect() |
sessionOrganizerStore | shared/state/sessionOrganizerStore.ts (host shared/state/sessionOrganizerStore/SessionOrganizerHost.tsx) | Session organization/categorization | folders, moveSession() |
worksStore | shared/state/worksStore.ts | Works management | works, createWork() |
musicWorksStore | shared/state/musicWorksStore.ts | Music works | tracks, playTrack() |
musicTemplateStore | shared/state/musicTemplateStore.ts | Music templates | templates |
VideoWorksContext | features/media/state/VideoWorksContext.tsx | Video works | videos |
videoTemplateStore | shared/state/videoTemplateStore.ts | Video templates | templates |
TemplateContext | features/marketplace/state/TemplateContext.tsx | Image templates | templates |
useWorldInfoHighlight | shared/hooks/characters/useWorldInfoHighlight.ts | WI keyword highlighting | highlightedKeywords |
Custom Hooks
60+ custom Hooks organized by functional domain:
Chat-Related
| Hook | File | Purpose |
|---|---|---|
useAppState | shared/hooks/useAppState.ts | Centrally manage view flags, active tab, user preferences |
useRouteSync | shared/hooks/useRouteSync.ts | Sync URL path to view state |
useSessionProtection | features/chat/hooks/useSessionProtection.ts | Active session protection (prevent refresh clear) |
useDraft | features/chat/hooks/useDraft.ts | Input draft save/restore |
useAutoUpdate | shared/hooks/useAutoUpdate.ts | App auto-update |
useAudioRecorder | features/media/hooks/useAudioRecorder.ts | Audio recording |
Feature Hook Directories
| Directory | Purpose | Representative Hooks |
|---|---|---|
features/chat/hooks/ | Chat operations | useMessageStream, useBranchNavigation |
features/marketplace/hooks/channel/ | Channel plugins | useChannelPlugins |
shared/hooks/characters/character/ | Character cards | useCharacterCards |
shared/hooks/characters/worldinfo/ | World info | useWorldInfo |
shared/hooks/characters/ | WI highlight / tags | useWorldInfoHighlight, useTagsData |
features/characters/hooks/sprites/ | Expression sprites | useSprites |
features/settings/components/tabs/tools-tab/hooks/ | MCP servers | useMcpManagement |
features/kb/hooks/ | Notes system | useNotesData |
features/todo/hooks/ | Todos | useTodoData |
features/tasks/hooks/ | Task management | useTasksData |
features/cron/hooks/ | Scheduled tasks | useCronJobs |
features/marketplace/hooks/skills/ | Skill management | useSkillHub |
Component Architecture
Layout Structure
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: Left fixed navigation rail, containing Home, Agents, Settings entry points
- WorkspaceShell: Main layout container, manages Rail + tab bar + content area
- TitleBar: Multi-tab bar + window control buttons (maximize/minimize/close)
- Main Content: Dynamically render page components per route
Workspace Extension (Workspace Registry)
The "workspace panel" between Chat/Agent pages (editor / git diff / HTML preview / canvas / design studio area to the right of file tree) extends via WorkspaceDefinition registry. WorkspaceArea.tsx as host shell only does layout + registry dispatch — not any activeView === 'X' business branches.
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]
Each WorkspaceDefinition self-manages:
- Own
viewidentifier +isAvailable(ctx)predicate - Own view-mode state (split/code/preview etc internal useState — doesn't leak to host)
- Publish toolbar config via
useWorkspaceToolbar(config)to slot; host's<WorkspaceToolbar>reads viauseWorkspaceToolbarConfig() - Optional
onClose(ctx, cb)hook (triggered by close button, runs outside React tree, so close-fn channel usesWorkspaceCallbacks)
Adding new workspace type = modify 2 files: add entry in registry.ts array + create definitions/<Name>Workspace.tsx. WorkspaceArea.tsx doesn't need changes. Detailed recipe in packages/renderer/CLAUDE.md "🧩 Add New Workspace Type" section, contract spec in openspec/specs/workspace-registry/spec.md.
Cross-view coordination (e.g., "Preview" button in editor jumping to htmlPreview view, HTML preview maximizing hides entire workspace pane) centralized in host-affordances.ts — only place where activeView === 'X' judgment allowed.
| File | Responsibility |
|---|---|
features/chat/components/content/WorkspaceArea.tsx | Host shell (layout + slot reader + registry dispatch, no business branches) |
features/chat/components/content/workspaces/types.ts | WorkspaceDefinition / WorkspaceHostContext / WorkspaceCallbacks contract |
features/chat/components/content/workspaces/registry.ts | WORKSPACE_REGISTRY single registration point + findActiveWorkspace query |
features/chat/components/content/workspaces/host-affordances.ts | Cross-view coordination (cross-view toggle + maximize hide) |
features/chat/components/content/workspaces/workspace-toolbar-slot{,-internals,-hooks}.{tsx,ts} | Toolbar slot trio (Provider / contexts / hooks) |
features/chat/components/content/workspaces/definitions/<Name>Workspace.tsx | 7 workspace definitions (editor / git / htmlPreview / a2ui / devPreview / designStudio / canvas) |
features/chat/components/content/workspaces/inline-file-viewers.tsx | InlineMarkdownViewer + InlineBinaryViewer (image/audio/video/pdf + "cannot be previewed" fallback) |
features/chat/components/content/workspaces/inline-file-viewers-types.ts | getBinaryFileType / isMarkdownFile / isNonTextFile file type detection |
features/chat/components/content/unified-page/hooks/useWorkspaceState.ts | Workspace state hook (.html → openHtmlPreview() routing + binary file routing in handleFileSelect) |
Routing System
Use HashRouter (Electron doesn't support BrowserRouter) with useRouteSync Hook to sync URL to view state:
// 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 Internationalization
Support 3 languages, using useTranslation() Hook:
| Language | Directory | Files |
|---|---|---|
| English | locales/en/ | 15+ JSON files |
| Chinese (Simplified) | locales/zh/ | 15+ JSON files |
| Japanese | locales/ja/ | 15+ JSON files |
Translation files split by functional domain: common.json, chat.json, settings.json, characters.json, elfi.json, etc.
AppState Hook
useAppState centrally manages global UI state:
// 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 Configuration
| Config | Value |
|---|---|
| Build tool | Vite 7.0 |
| Styling | Tailwind CSS 3.4 + CSS variables (semantic tokens) |
| Code splitting | Route-level lazy loading (lazy/ directory, 6 files + 1 barrel by purpose) |
| Tree shaking | Named imports |
| Main bundle limit | 500 KB |
Related Files
| File | Description |
|---|---|
packages/renderer/src/app/App.tsx | App entry, Provider hierarchy and layout |
packages/renderer/src/shared/state/chatStore.ts | Zustand chat state Store |
packages/renderer/src/shared/state/settingsStore.ts | Zustand settings state Store |
packages/renderer/src/shared/state/chatStore.ts | Unified chat API (old UnifiedChatContext migrated to chatStore) |
packages/renderer/src/shared/state/themeStore.ts | Theme Store (old ThemeContext) |
packages/renderer/src/shared/hooks/useAppState.ts | Centralized UI state management |
packages/renderer/src/shared/hooks/useRouteSync.ts | Route sync |
packages/renderer/src/app/layout/WorkspaceShell.tsx | Main layout container |
packages/renderer/src/app/layout/WorkspaceRail.tsx | Left navigation rail |
packages/renderer/src/app/title-bar/ | Tab bar components |
packages/renderer/src/app/lazy/ | Lazy load component directory (pages.tsx / workspaces.tsx / inline.tsx / shell.tsx / skeletons.tsx / with-suspense.tsx, barrel in lazy/index.ts) |
packages/renderer/src/features/chat/components/content/WorkspaceArea.tsx | Central workspace host shell (layout + registry dispatch, no business branches) |
packages/renderer/src/features/chat/components/content/workspaces/ | Workspace registry (WorkspaceDefinition contract + 7 definition files + toolbar slot + host-affordances) |
packages/renderer/src/app/railItems.ts | Navigation rail config |
packages/renderer/src/app/mainContentRouter.tsx | Main content router parsing |
packages/renderer/CLAUDE.md | Frontend UI development standards |