perf: improve application startup performance

Renderer code splitting:
- Lazy-load 15 components with React.lazy() and Suspense (ChatPane,
  ActivityPanel, SettingsPanel, TerminalPanel, WelcomePane, BottomPanel,
  GitPanel, CommandPalette, CommitComposer, WorkflowPicker, BookmarksPanel,
  SessionSearchPanel, KeyboardShortcutsPanel, DiscoveredToolingModal,
  ProjectSettingsPanel)
- This moves ~1.2 MB of optional dependencies (Lexical, @xyflow/react,
  @xterm/xterm, highlight.js, motion) out of the critical bundle
- Critical bundle reduced from ~1.5 MB to ~759 KB
- Defer JetBrains Mono font load to TerminalPanel (saves ~80 KB at startup)

Main process optimizations:
- Use show: false + ready-to-show on BrowserWindow to eliminate blank flash
- Decouple workspace loading from bootstrap — no longer blocks window, tray,
  and auto-update setup
- Defer sidecar-dependent approval tool pruning to run in background after
  workspace is returned to renderer (removes sidecar spawn from critical path)
- Parallelize independent workspace sync operations (user tooling, project
  tooling, project customization) with Promise.all()

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
David Kaya
2026-04-13 08:52:45 +02:00
co-authored by Copilot
parent 574455729b
commit e3660254df
6 changed files with 245 additions and 175 deletions
+25 -9
View File
@@ -473,27 +473,43 @@ export class AryxAppService extends EventEmitter<AppServiceEvents> {
const selectedProject = selectedProjectId const selectedProject = selectedProjectId
? this.workspace.projects.find((project) => project.id === selectedProjectId) ? this.workspace.projects.find((project) => project.id === selectedProjectId)
: undefined; : undefined;
const didSyncUserTooling = await this.syncUserDiscoveredTooling(this.workspace);
const didSyncProjectTooling = selectedProject // Run independent sync operations in parallel
? await this.syncProjectDiscoveredTooling(this.workspace, selectedProject) const [didSyncUserTooling, didSyncProjectTooling, didSyncProjectCustomization] = await Promise.all([
: false; this.syncUserDiscoveredTooling(this.workspace),
const didSyncProjectCustomization = selectedProject selectedProject
? await this.syncProjectCustomization(selectedProject) ? this.syncProjectDiscoveredTooling(this.workspace, selectedProject)
: false; : false,
selectedProject
? this.syncProjectCustomization(selectedProject)
: false,
]);
const didPruneSelections = this.pruneUnavailableSessionToolingSelections(this.workspace); const didPruneSelections = this.pruneUnavailableSessionToolingSelections(this.workspace);
const didPruneApprovalTools = await this.pruneUnavailableApprovalTools(this.workspace);
if ( if (
didSyncUserTooling didSyncUserTooling
|| didSyncProjectTooling || didSyncProjectTooling
|| didSyncProjectCustomization || didSyncProjectCustomization
|| didPruneSelections || didPruneSelections
|| didPruneApprovalTools
|| this.cleanupInterruptedSessions(this.workspace) || this.cleanupInterruptedSessions(this.workspace)
) { ) {
await this.workspaceRepository.save(this.workspace); await this.workspaceRepository.save(this.workspace);
} }
await this.syncProjectCustomizationWatchers(this.workspace); await this.syncProjectCustomizationWatchers(this.workspace);
// Defer sidecar-dependent approval pruning so it doesn't block startup.
// This runs in the background and emits workspace-updated when done.
void this.pruneUnavailableApprovalTools(this.workspace)
.then(async (didPrune) => {
if (didPrune && this.workspace) {
await this.workspaceRepository.save(this.workspace);
this.emit('workspace-updated', this.workspace);
}
})
.catch((error) => {
console.error('[aryx startup] deferred approval tool pruning failed', error);
});
} }
if (!this.didScheduleInitialProjectGitRefresh) { if (!this.didScheduleInitialProjectGitRefresh) {
+27 -19
View File
@@ -23,21 +23,34 @@ async function bootstrap(): Promise<void> {
mainWindow = createMainWindow(); mainWindow = createMainWindow();
registerIpcHandlers(mainWindow, appService, autoUpdateService); registerIpcHandlers(mainWindow, appService, autoUpdateService);
// Apply persisted theme to the title bar overlay // Start workspace loading in parallel — don't block window from showing.
const workspace = await appService.loadWorkspace(); // The renderer fetches the workspace via its own IPC call after mount.
applyTitleBarTheme(mainWindow, workspace.settings.theme); const workspaceReady = appService.loadWorkspace();
// Set up system tray // Apply theme and set up tray once workspace is available
systemTray = new SystemTray({ workspaceReady
onShowWindow: showAndFocusWindow, .then((workspace) => {
onCreateScratchpad: () => { if (!mainWindow) return;
showAndFocusWindow(); applyTitleBarTheme(mainWindow, workspace.settings.theme);
mainWindow?.webContents.send('tray:create-scratchpad');
}, systemTray = new SystemTray({
onQuit: () => app.quit(), onShowWindow: showAndFocusWindow,
}); onCreateScratchpad: () => {
systemTray.create(); showAndFocusWindow();
systemTray.updateRunningCount(workspace); mainWindow?.webContents.send('tray:create-scratchpad');
},
onQuit: () => app.quit(),
});
systemTray.create();
systemTray.updateRunningCount(workspace);
appService!.on('workspace-updated', (updatedWorkspace) => {
systemTray?.updateRunningCount(updatedWorkspace);
});
})
.catch((error) => {
console.error('[aryx bootstrap] workspace load failed', error);
});
// Intercept close to hide to tray when the setting is enabled // Intercept close to hide to tray when the setting is enabled
setupCloseToTray(mainWindow, () => { setupCloseToTray(mainWindow, () => {
@@ -45,11 +58,6 @@ async function bootstrap(): Promise<void> {
return currentWorkspace?.settings.minimizeToTray === true; return currentWorkspace?.settings.minimizeToTray === true;
}); });
// Keep tray status in sync when workspace changes
appService.on('workspace-updated', (updatedWorkspace) => {
systemTray?.updateRunningCount(updatedWorkspace);
});
if (!app.isPackaged) { if (!app.isPackaged) {
mainWindow.webContents.openDevTools({ mode: 'detach' }); mainWindow.webContents.openDevTools({ mode: 'detach' });
} }
+5
View File
@@ -14,6 +14,7 @@ export function createMainWindow(): BrowserWindowType {
height: 960, height: 960,
minWidth: 1120, minWidth: 1120,
minHeight: 720, minHeight: 720,
show: false,
title: 'aryx', title: 'aryx',
icon: resolveWindowIconPath({ icon: resolveWindowIconPath({
appPath: app.getAppPath(), appPath: app.getAppPath(),
@@ -36,6 +37,10 @@ export function createMainWindow(): BrowserWindowType {
}, },
}); });
window.once('ready-to-show', () => {
window.show();
});
const rendererUrl = process.env.ELECTRON_RENDERER_URL; const rendererUrl = process.env.ELECTRON_RENDERER_URL;
if (rendererUrl) { if (rendererUrl) {
+187 -146
View File
@@ -1,20 +1,7 @@
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { lazy, Suspense, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { CommitComposer } from '@renderer/components/chat/CommitComposer';
import { AppShell } from '@renderer/components/AppShell'; import { AppShell } from '@renderer/components/AppShell';
import { ActivityPanel } from '@renderer/components/ActivityPanel';
import { ChatPane } from '@renderer/components/ChatPane';
import { CommandPalette } from '@renderer/components/CommandPalette';
import { DiscoveredToolingModal } from '@renderer/components/DiscoveredToolingModal';
import { KeyboardShortcutsPanel } from '@renderer/components/KeyboardShortcutsPanel';
import { ProjectSettingsPanel } from '@renderer/components/ProjectSettingsPanel';
import { BookmarksPanel } from '@renderer/components/BookmarksPanel';
import { SessionSearchPanel } from '@renderer/components/SessionSearchPanel';
import { SettingsPanel, type SettingsSection } from '@renderer/components/SettingsPanel';
import { Sidebar } from '@renderer/components/Sidebar'; import { Sidebar } from '@renderer/components/Sidebar';
import { BottomPanel, DEFAULT_HEIGHT as DEFAULT_BOTTOM_HEIGHT, MIN_HEIGHT as MIN_BOTTOM_HEIGHT, type BottomPanelTab } from '@renderer/components/BottomPanel';
import { GitPanel } from '@renderer/components/GitPanel';
import { TerminalPanel } from '@renderer/components/TerminalPanel';
import { resolveChatToolingSettings } from '@renderer/lib/chatTooling'; import { resolveChatToolingSettings } from '@renderer/lib/chatTooling';
import { import {
applySessionEventActivity, applySessionEventActivity,
@@ -33,7 +20,6 @@ import {
} from '@renderer/lib/sessionActivity'; } from '@renderer/lib/sessionActivity';
import { applySubagentEvent, pruneSubagentMap, type ActiveSubagentMap } from '@renderer/lib/subagentTracker'; import { applySubagentEvent, pruneSubagentMap, type ActiveSubagentMap } from '@renderer/lib/subagentTracker';
import { applySessionEventWorkspace } from '@renderer/lib/sessionWorkspace'; import { applySessionEventWorkspace } from '@renderer/lib/sessionWorkspace';
import { WelcomePane } from '@renderer/components/WelcomePane';
import { getElectronApi } from '@renderer/lib/electronApi'; import { getElectronApi } from '@renderer/lib/electronApi';
import { useTheme, useSidecarCapabilities } from '@renderer/hooks/useAppHooks'; import { useTheme, useSidecarCapabilities } from '@renderer/hooks/useAppHooks';
import { import {
@@ -52,7 +38,33 @@ import type { WorkspaceAgentDefinition } from '@shared/domain/workspaceAgent';
import type { WorkspaceState } from '@shared/domain/workspace'; import type { WorkspaceState } from '@shared/domain/workspace';
import type { UpdateStatus } from '@shared/contracts/ipc'; import type { UpdateStatus } from '@shared/contracts/ipc';
import { createId, nowIso } from '@shared/utils/ids'; import { createId, nowIso } from '@shared/utils/ids';
import { WorkflowPicker } from '@renderer/components/workflow/WorkflowPicker';
// Lazy-loaded components — kept off the critical startup bundle.
// These pull in heavy dependencies (Lexical, @xyflow/react, @xterm/xterm, motion, etc.)
// that are not needed until the user interacts with the corresponding feature.
const ActivityPanel = lazy(() => import('@renderer/components/ActivityPanel').then((m) => ({ default: m.ActivityPanel })));
const BookmarksPanel = lazy(() => import('@renderer/components/BookmarksPanel').then((m) => ({ default: m.BookmarksPanel })));
const BottomPanel = lazy(() => import('@renderer/components/BottomPanel').then((m) => ({ default: m.BottomPanel })));
const ChatPane = lazy(() => import('@renderer/components/ChatPane').then((m) => ({ default: m.ChatPane })));
const CommandPalette = lazy(() => import('@renderer/components/CommandPalette').then((m) => ({ default: m.CommandPalette })));
const CommitComposer = lazy(() => import('@renderer/components/chat/CommitComposer').then((m) => ({ default: m.CommitComposer })));
const DiscoveredToolingModal = lazy(() => import('@renderer/components/DiscoveredToolingModal').then((m) => ({ default: m.DiscoveredToolingModal })));
const GitPanel = lazy(() => import('@renderer/components/GitPanel').then((m) => ({ default: m.GitPanel })));
const KeyboardShortcutsPanel = lazy(() => import('@renderer/components/KeyboardShortcutsPanel').then((m) => ({ default: m.KeyboardShortcutsPanel })));
const ProjectSettingsPanel = lazy(() => import('@renderer/components/ProjectSettingsPanel').then((m) => ({ default: m.ProjectSettingsPanel })));
const SessionSearchPanel = lazy(() => import('@renderer/components/SessionSearchPanel').then((m) => ({ default: m.SessionSearchPanel })));
const SettingsPanel = lazy(() => import('@renderer/components/SettingsPanel').then((m) => ({ default: m.SettingsPanel })));
const TerminalPanel = lazy(() => import('@renderer/components/TerminalPanel').then((m) => ({ default: m.TerminalPanel })));
const WelcomePane = lazy(() => import('@renderer/components/WelcomePane').then((m) => ({ default: m.WelcomePane })));
const WorkflowPicker = lazy(() => import('@renderer/components/workflow/WorkflowPicker').then((m) => ({ default: m.WorkflowPicker })));
// Re-export type-only imports from lazy modules so they're available without pulling in the bundle
type SettingsSection = 'appearance' | 'connection' | 'workflows' | 'agents' | 'mcp-servers' | 'lsp-profiles' | 'troubleshooting';
type BottomPanelTab = 'terminal' | 'git';
// Constants duplicated from BottomPanel to avoid importing the full module at startup
const DEFAULT_BOTTOM_HEIGHT = 280;
const MIN_BOTTOM_HEIGHT = 120;
function createDraftMcpServer(): McpServerDefinition { function createDraftMcpServer(): McpServerDefinition {
const timestamp = nowIso(); const timestamp = nowIso();
@@ -643,6 +655,9 @@ export default function App() {
); );
} }
// Suspense fallback for lazy-loaded panels — intentionally blank to avoid layout flash
const lazyFallback = null;
// Determine main content // Determine main content
let content: React.ReactNode; let content: React.ReactNode;
let detailPanel: React.ReactNode | undefined; let detailPanel: React.ReactNode | undefined;
@@ -657,6 +672,7 @@ export default function App() {
); );
} else if (selectedSession && workflowForSession && projectForSession) { } else if (selectedSession && workflowForSession && projectForSession) {
content = ( content = (
<Suspense fallback={lazyFallback}>
<ChatPane <ChatPane
onSend={(c, attachments, messageMode, promptInvocation) => api.sendSessionMessage({ onSend={(c, attachments, messageMode, promptInvocation) => api.sendSessionMessage({
sessionId: selectedSession.id, sessionId: selectedSession.id,
@@ -740,30 +756,36 @@ export default function App() {
onDiscardRunChanges={handleDiscardRunChanges} onDiscardRunChanges={handleDiscardRunChanges}
onOpenCommitComposer={handleOpenCommitComposer} onOpenCommitComposer={handleOpenCommitComposer}
/> />
</Suspense>
); );
detailPanel = ( detailPanel = (
<ActivityPanel <Suspense fallback={lazyFallback}>
activity={activityForSession} <ActivityPanel
workflow={workflowForSession} activity={activityForSession}
session={selectedSession} workflow={workflowForSession}
sessionRequestUsage={requestUsageForSession} session={selectedSession}
turnEvents={turnEventsForSession} sessionRequestUsage={requestUsageForSession}
/> turnEvents={turnEventsForSession}
/>
</Suspense>
); );
} else { } else {
content = ( content = (
<WelcomePane <Suspense fallback={lazyFallback}>
hasProjects={hasUserProjects} <WelcomePane
connectionStatus={sidecarCapabilities?.connection.status} hasProjects={hasUserProjects}
onAddProject={() => void api.addProject()} connectionStatus={sidecarCapabilities?.connection.status}
onNewScratchpad={() => handleCreateScratchpad()} onAddProject={() => void api.addProject()}
onOpenSettings={() => setShowSettings(true)} onNewScratchpad={() => handleCreateScratchpad()}
/> onOpenSettings={() => setShowSettings(true)}
/>
</Suspense>
); );
} }
// Settings overlay // Settings overlay
const overlay = showSettings ? ( const overlay = showSettings ? (
<Suspense fallback={lazyFallback}>
<SettingsPanel <SettingsPanel
availableModels={availableModels} availableModels={availableModels}
initialSection={settingsSection} initialSection={settingsSection}
@@ -836,6 +858,7 @@ export default function App() {
}} }}
onGetQuota={() => api.getQuota()} onGetQuota={() => api.getQuota()}
/> />
</Suspense>
) : null; ) : null;
return ( return (
@@ -846,29 +869,31 @@ export default function App() {
overlay={overlay} overlay={overlay}
bottomPanel={ bottomPanel={
bottomPanelOpen ? ( bottomPanelOpen ? (
<BottomPanel <Suspense fallback={lazyFallback}>
activeTab={bottomPanelTab} <BottomPanel
gitContent={ activeTab={bottomPanelTab}
selectedSession && !isScratchpadProject(selectedSession.projectId) ? ( gitContent={
<GitPanel selectedSession && !isScratchpadProject(selectedSession.projectId) ? (
onDirtyChange={setGitDirty} <GitPanel
projectId={selectedSession.projectId} onDirtyChange={setGitDirty}
/> projectId={selectedSession.projectId}
) : ( />
<div className="flex items-center justify-center py-8 text-[11px] text-[var(--color-text-muted)]"> ) : (
Git is not available for scratchpad sessions <div className="flex items-center justify-center py-8 text-[11px] text-[var(--color-text-muted)]">
</div> Git is not available for scratchpad sessions
) </div>
} )
gitDirty={gitDirty} }
height={bottomPanelHeight} gitDirty={gitDirty}
onClose={handleBottomPanelClose} height={bottomPanelHeight}
onHeightChange={handleBottomPanelHeightChange} onClose={handleBottomPanelClose}
onTabChange={setBottomPanelTab} onHeightChange={handleBottomPanelHeightChange}
showGitTab={!!selectedSession && !isScratchpadProject(selectedSession.projectId)} onTabChange={setBottomPanelTab}
terminalContent={<TerminalPanel onRunningChange={setTerminalRunning} />} showGitTab={!!selectedSession && !isScratchpadProject(selectedSession.projectId)}
terminalRunning={terminalRunning} terminalContent={<TerminalPanel onRunningChange={setTerminalRunning} />}
/> terminalRunning={terminalRunning}
/>
</Suspense>
) : undefined ) : undefined
} }
sidebar={ sidebar={
@@ -911,120 +936,136 @@ export default function App() {
/> />
{showDiscoveryModal && ( {showDiscoveryModal && (
<DiscoveredToolingModal <Suspense fallback={lazyFallback}>
onClose={() => setShowDiscoveryModal(false)} <DiscoveredToolingModal
onResolveProjectServers={(serverIds, resolution) => { onClose={() => setShowDiscoveryModal(false)}
if (selectedProject) { onResolveProjectServers={(serverIds, resolution) => {
void api.resolveProjectDiscoveredTooling({ projectId: selectedProject.id, serverIds, resolution }); if (selectedProject) {
} void api.resolveProjectDiscoveredTooling({ projectId: selectedProject.id, serverIds, resolution });
}} }
onResolveUserServers={(serverIds, resolution) => { }}
void api.resolveWorkspaceDiscoveredTooling({ serverIds, resolution }); onResolveUserServers={(serverIds, resolution) => {
}} void api.resolveWorkspaceDiscoveredTooling({ serverIds, resolution });
projectDiscoveredTooling={selectedProject?.discoveredTooling} }}
projectName={selectedProject?.name} projectDiscoveredTooling={selectedProject?.discoveredTooling}
userDiscoveredTooling={workspace.settings.discoveredUserTooling} projectName={selectedProject?.name}
/> userDiscoveredTooling={workspace.settings.discoveredUserTooling}
/>
</Suspense>
)} )}
{projectForSettings && ( {projectForSettings && (
<ProjectSettingsPanel <Suspense fallback={lazyFallback}>
project={projectForSettings} <ProjectSettingsPanel
onClose={() => setProjectSettingsId(undefined)} project={projectForSettings}
onRescanConfigs={() => { onClose={() => setProjectSettingsId(undefined)}
void api.rescanProjectConfigs({ projectId: projectForSettings.id }); onRescanConfigs={() => {
}} void api.rescanProjectConfigs({ projectId: projectForSettings.id });
onRescanCustomization={() => { }}
void api.rescanProjectCustomization({ projectId: projectForSettings.id }); onRescanCustomization={() => {
}} void api.rescanProjectCustomization({ projectId: projectForSettings.id });
onResolveDiscoveredTooling={(serverIds, resolution) => { }}
void api.resolveProjectDiscoveredTooling({ projectId: projectForSettings.id, serverIds, resolution }); onResolveDiscoveredTooling={(serverIds, resolution) => {
}} void api.resolveProjectDiscoveredTooling({ projectId: projectForSettings.id, serverIds, resolution });
onSetAgentProfileEnabled={(agentProfileId, enabled) => { }}
void api.setProjectAgentProfileEnabled({ projectId: projectForSettings.id, agentProfileId, enabled }); onSetAgentProfileEnabled={(agentProfileId, enabled) => {
}} void api.setProjectAgentProfileEnabled({ projectId: projectForSettings.id, agentProfileId, enabled });
onRemoveProject={() => { }}
void api.removeProject(projectForSettings.id); onRemoveProject={() => {
setProjectSettingsId(undefined); void api.removeProject(projectForSettings.id);
}} setProjectSettingsId(undefined);
/> }}
/>
</Suspense>
)} )}
{commandPaletteOpen && workspace && ( {commandPaletteOpen && workspace && (
<CommandPalette <Suspense fallback={lazyFallback}>
workspace={workspace} <CommandPalette
onClose={() => setCommandPaletteOpen(false)} workspace={workspace}
onSelectSession={(sessionId) => { onClose={() => setCommandPaletteOpen(false)}
void api.selectSession(sessionId); onSelectSession={(sessionId) => {
}} void api.selectSession(sessionId);
onSelectProject={(projectId) => { }}
void api.selectProject(projectId); onSelectProject={(projectId) => {
}} void api.selectProject(projectId);
onNewSession={(projectId) => handleNewSession(projectId)} }}
onCreateScratchpad={handleCreateScratchpad} onNewSession={(projectId) => handleNewSession(projectId)}
onOpenSettings={() => setShowSettings(true)} onCreateScratchpad={handleCreateScratchpad}
onOpenProjectSettings={(projectId) => setProjectSettingsId(projectId)} onOpenSettings={() => setShowSettings(true)}
onToggleTerminal={handleTerminalToggle} onOpenProjectSettings={(projectId) => setProjectSettingsId(projectId)}
onSetTheme={(theme) => void api.setTheme(theme)} onToggleTerminal={handleTerminalToggle}
onDuplicateSession={(sessionId) => { onSetTheme={(theme) => void api.setTheme(theme)}
void api.duplicateSession({ sessionId }); onDuplicateSession={(sessionId) => {
}} void api.duplicateSession({ sessionId });
onPinSession={(sessionId, isPinned) => { }}
void api.setSessionPinned({ sessionId, isPinned }); onPinSession={(sessionId, isPinned) => {
}} void api.setSessionPinned({ sessionId, isPinned });
onArchiveSession={(sessionId, isArchived) => { }}
void api.setSessionArchived({ sessionId, isArchived }); onArchiveSession={(sessionId, isArchived) => {
}} void api.setSessionArchived({ sessionId, isArchived });
onAddProject={() => void api.addProject()} }}
onOpenAppDataFolder={() => void api.openAppDataFolder()} onAddProject={() => void api.addProject()}
onShowShortcuts={() => setShowShortcuts(true)} onOpenAppDataFolder={() => void api.openAppDataFolder()}
onShowSearch={() => setShowSearch(true)} onShowShortcuts={() => setShowShortcuts(true)}
onShowBookmarks={() => setShowBookmarks(true)} onShowSearch={() => setShowSearch(true)}
/> onShowBookmarks={() => setShowBookmarks(true)}
/>
</Suspense>
)} )}
{showShortcuts && ( {showShortcuts && (
<KeyboardShortcutsPanel onClose={() => setShowShortcuts(false)} /> <Suspense fallback={lazyFallback}>
<KeyboardShortcutsPanel onClose={() => setShowShortcuts(false)} />
</Suspense>
)} )}
{showSearch && workspace && ( {showSearch && workspace && (
<SessionSearchPanel <Suspense fallback={lazyFallback}>
workspace={workspace} <SessionSearchPanel
onClose={() => setShowSearch(false)} workspace={workspace}
onSelectSession={(sessionId) => { onClose={() => setShowSearch(false)}
void api.selectSession(sessionId); onSelectSession={(sessionId) => {
}} void api.selectSession(sessionId);
/> }}
/>
</Suspense>
)} )}
{showBookmarks && workspace && ( {showBookmarks && workspace && (
<BookmarksPanel <Suspense fallback={lazyFallback}>
workspace={workspace} <BookmarksPanel
onClose={() => setShowBookmarks(false)} workspace={workspace}
onSelectSession={(sessionId) => { onClose={() => setShowBookmarks(false)}
void api.selectSession(sessionId); onSelectSession={(sessionId) => {
}} void api.selectSession(sessionId);
onUnpinMessage={(sessionId, messageId) => { }}
void api.setSessionMessagePinned({ sessionId, messageId, isPinned: false }); onUnpinMessage={(sessionId, messageId) => {
}} void api.setSessionMessagePinned({ sessionId, messageId, isPinned: false });
/> }}
/>
</Suspense>
)} )}
{commitComposerCtx && ( {commitComposerCtx && (
<CommitComposer <Suspense fallback={lazyFallback}>
onClose={() => setCommitComposerCtx(undefined)} <CommitComposer
projectId={commitComposerCtx.projectId} onClose={() => setCommitComposerCtx(undefined)}
runId={commitComposerCtx.runId} projectId={commitComposerCtx.projectId}
sessionId={commitComposerCtx.sessionId} runId={commitComposerCtx.runId}
/> sessionId={commitComposerCtx.sessionId}
/>
</Suspense>
)} )}
{workflowPickerProjectId && workspace && ( {workflowPickerProjectId && workspace && (
<WorkflowPicker <Suspense fallback={lazyFallback}>
workflows={workspace.workflows} <WorkflowPicker
onSelect={handleWorkflowPicked} workflows={workspace.workflows}
onClose={() => setWorkflowPickerProjectId(null)} onSelect={handleWorkflowPicked}
/> onClose={() => setWorkflowPickerProjectId(null)}
/>
</Suspense>
)} )}
</> </>
); );
@@ -3,6 +3,7 @@ import { RotateCcw } from 'lucide-react';
import { Terminal } from '@xterm/xterm'; import { Terminal } from '@xterm/xterm';
import { FitAddon } from '@xterm/addon-fit'; import { FitAddon } from '@xterm/addon-fit';
import '@xterm/xterm/css/xterm.css'; import '@xterm/xterm/css/xterm.css';
import '@fontsource-variable/jetbrains-mono';
import { getElectronApi } from '@renderer/lib/electronApi'; import { getElectronApi } from '@renderer/lib/electronApi';
import type { TerminalSnapshot } from '@shared/domain/terminal'; import type { TerminalSnapshot } from '@shared/domain/terminal';
-1
View File
@@ -3,7 +3,6 @@ import { createRoot } from 'react-dom/client';
import '@fontsource-variable/outfit'; import '@fontsource-variable/outfit';
import '@fontsource-variable/dm-sans'; import '@fontsource-variable/dm-sans';
import '@fontsource-variable/jetbrains-mono';
import App from '@renderer/App'; import App from '@renderer/App';
import '@renderer/styles.css'; import '@renderer/styles.css';