Skip to main content

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);
StoreFileResponsibility
chatStoreshared/state/chatStore.tsSessions, messages, streaming state, branches
settingsStoreshared/state/settingsStore.tsSettings state
useChatStoreSyncshared/state/useChatStoreSync.tsChatDataContext → chatStore sync

React Context Details

23+ Contexts, each with independent responsibility:

ContextFileResponsibilityCore State/Methods
LocaleContextshared/state/LocaleContext.tsxi18n (en/zh/ja)useTranslation(), setLocale()
themeStoreshared/state/themeStore.ts (host app/ThemeHost.tsx)Theme mode, wallpaper, custom CSSmode, resolvedMode, userTheme, customCss
ConfirmDialogHostshared/state/ConfirmDialogHost.tsxGlobal confirm dialogconfirm(options)
authStoreshared/state/authStore.tsAuth stateuser, isAuthenticated, login(), logout()
SettingsDataHostapp/SettingsDataHost.tsxSettings read/writesettings, updateSetting()
ElfiContextfeatures/elfi/state/ElfiContext.tsxElfi smart assistantisOpen, messages, sendMessage()
webSearchStoreshared/state/webSearchStore.ts (host app/WebSearchHost.tsx)Web searchsearch(), results
chatStoreshared/state/chatStore.tsChat data cache + unified chat API (old ChatDataContext / UnifiedChatContext migrated here)sessions, messages, sendMessage(), regenerate(), editMessage()
htmlPreviewStoreshared/state/htmlPreviewStore.ts (host app/HtmlPreviewHost.tsx)HTML content previewshowPreview(), previewContent
devPreviewStoreshared/state/devPreviewStore.ts (host app/DevPreviewHost.tsx)Dev mode previewisDevMode, devData
ChatTabsContextfeatures/chat/state/ChatTabsContext.tsxMulti-tab chat managementtabs, activeTabId, createTab(), closeTab()
AgentContextfeatures/agents/state/AgentContext.tsxAgent stateagents, selectedAgent
subagentStoreshared/state/subagentStore.ts (host shared/state/SubagentHost.tsx)Subagent statesubagents, status
MessageSelectionContextfeatures/chat/state/MessageSelectionContext.tsxMessage multi-selectselectedIds, toggleSelect()
sessionOrganizerStoreshared/state/sessionOrganizerStore.ts (host shared/state/sessionOrganizerStore/SessionOrganizerHost.tsx)Session organization/categorizationfolders, moveSession()
worksStoreshared/state/worksStore.tsWorks managementworks, createWork()
musicWorksStoreshared/state/musicWorksStore.tsMusic workstracks, playTrack()
musicTemplateStoreshared/state/musicTemplateStore.tsMusic templatestemplates
VideoWorksContextfeatures/media/state/VideoWorksContext.tsxVideo worksvideos
videoTemplateStoreshared/state/videoTemplateStore.tsVideo templatestemplates
TemplateContextfeatures/marketplace/state/TemplateContext.tsxImage templatestemplates
useWorldInfoHighlightshared/hooks/characters/useWorldInfoHighlight.tsWI keyword highlightinghighlightedKeywords

Custom Hooks

60+ custom Hooks organized by functional domain:

HookFilePurpose
useAppStateshared/hooks/useAppState.tsCentrally manage view flags, active tab, user preferences
useRouteSyncshared/hooks/useRouteSync.tsSync URL path to view state
useSessionProtectionfeatures/chat/hooks/useSessionProtection.tsActive session protection (prevent refresh clear)
useDraftfeatures/chat/hooks/useDraft.tsInput draft save/restore
useAutoUpdateshared/hooks/useAutoUpdate.tsApp auto-update
useAudioRecorderfeatures/media/hooks/useAudioRecorder.tsAudio recording

Feature Hook Directories

DirectoryPurposeRepresentative Hooks
features/chat/hooks/Chat operationsuseMessageStream, useBranchNavigation
features/marketplace/hooks/channel/Channel pluginsuseChannelPlugins
shared/hooks/characters/character/Character cardsuseCharacterCards
shared/hooks/characters/worldinfo/World infouseWorldInfo
shared/hooks/characters/WI highlight / tagsuseWorldInfoHighlight, useTagsData
features/characters/hooks/sprites/Expression spritesuseSprites
features/settings/components/tabs/tools-tab/hooks/MCP serversuseMcpManagement
features/kb/hooks/Notes systemuseNotesData
features/todo/hooks/TodosuseTodoData
features/tasks/hooks/Task managementuseTasksData
features/cron/hooks/Scheduled tasksuseCronJobs
features/marketplace/hooks/skills/Skill managementuseSkillHub

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 view identifier + 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 via useWorkspaceToolbarConfig()
  • Optional onClose(ctx, cb) hook (triggered by close button, runs outside React tree, so close-fn channel uses WorkspaceCallbacks)

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.

FileResponsibility
features/chat/components/content/WorkspaceArea.tsxHost shell (layout + slot reader + registry dispatch, no business branches)
features/chat/components/content/workspaces/types.tsWorkspaceDefinition / WorkspaceHostContext / WorkspaceCallbacks contract
features/chat/components/content/workspaces/registry.tsWORKSPACE_REGISTRY single registration point + findActiveWorkspace query
features/chat/components/content/workspaces/host-affordances.tsCross-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.tsx7 workspace definitions (editor / git / htmlPreview / a2ui / devPreview / designStudio / canvas)
features/chat/components/content/workspaces/inline-file-viewers.tsxInlineMarkdownViewer + InlineBinaryViewer (image/audio/video/pdf + "cannot be previewed" fallback)
features/chat/components/content/workspaces/inline-file-viewers-types.tsgetBinaryFileType / isMarkdownFile / isNonTextFile file type detection
features/chat/components/content/unified-page/hooks/useWorkspaceState.tsWorkspace state hook (.htmlopenHtmlPreview() 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:

LanguageDirectoryFiles
Englishlocales/en/15+ JSON files
Chinese (Simplified)locales/zh/15+ JSON files
Japaneselocales/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

ConfigValue
Build toolVite 7.0
StylingTailwind CSS 3.4 + CSS variables (semantic tokens)
Code splittingRoute-level lazy loading (lazy/ directory, 6 files + 1 barrel by purpose)
Tree shakingNamed imports
Main bundle limit500 KB
FileDescription
packages/renderer/src/app/App.tsxApp entry, Provider hierarchy and layout
packages/renderer/src/shared/state/chatStore.tsZustand chat state Store
packages/renderer/src/shared/state/settingsStore.tsZustand settings state Store
packages/renderer/src/shared/state/chatStore.tsUnified chat API (old UnifiedChatContext migrated to chatStore)
packages/renderer/src/shared/state/themeStore.tsTheme Store (old ThemeContext)
packages/renderer/src/shared/hooks/useAppState.tsCentralized UI state management
packages/renderer/src/shared/hooks/useRouteSync.tsRoute sync
packages/renderer/src/app/layout/WorkspaceShell.tsxMain layout container
packages/renderer/src/app/layout/WorkspaceRail.tsxLeft 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.tsxCentral 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.tsNavigation rail config
packages/renderer/src/app/mainContentRouter.tsxMain content router parsing
packages/renderer/CLAUDE.mdFrontend UI development standards