Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 42 additions & 10 deletions src/apps/desktop/src/api/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2026,6 +2026,48 @@ pub async fn initialize_workspace_startup_state(
startup_trace: State<'_, DesktopStartupTrace>,
) -> Result<WorkspaceStartupStateSnapshotDto, String> {
let command_started = Instant::now();
let result =
initialize_workspace_startup_state_impl(&state, &app, &startup_trace, command_started)
.await;
startup_trace.record_tauri_command_elapsed(
"initialize_workspace_startup_state",
None,
command_started,
);
result
}

pub async fn prepare_workspace_startup_bootstrap_snapshot(
state: &State<'_, AppState>,
app: &tauri::AppHandle,
startup_trace: &State<'_, DesktopStartupTrace>,
) -> Option<WorkspaceStartupStateSnapshotDto> {
let started = Instant::now();
let snapshot =
initialize_workspace_startup_state_impl(state, app, startup_trace, started).await;
startup_trace.record_elapsed_step(
"native_setup",
"prepare_workspace_startup_bootstrap_snapshot",
started,
);
match snapshot {
Ok(snapshot) => Some(snapshot),
Err(error) => {
warn!(
"Failed to prepare workspace startup bootstrap snapshot, frontend will fall back to startup command: {}",
error
);
None
}
}
}

async fn initialize_workspace_startup_state_impl(
state: &State<'_, AppState>,
app: &tauri::AppHandle,
startup_trace: &State<'_, DesktopStartupTrace>,
command_started: Instant,
) -> Result<WorkspaceStartupStateSnapshotDto, String> {
let trace = startup_trace.inner();

initialize_global_state_impl(&state, &app, trace).await;
Expand All @@ -2042,11 +2084,6 @@ pub async fn initialize_workspace_startup_state(
{
Ok(removed_count) => removed_count,
Err(error) => {
startup_trace.record_tauri_command_elapsed(
"initialize_workspace_startup_state",
None,
command_started,
);
return Err(error);
}
};
Expand All @@ -2058,11 +2095,6 @@ pub async fn initialize_workspace_startup_state(
"initialize_workspace_startup_state.collect_workspace_state_snapshot",
snapshot_started,
);
startup_trace.record_tauri_command_elapsed(
"initialize_workspace_startup_state",
None,
command_started,
);

Ok(WorkspaceStartupStateSnapshotDto {
cleanup_removed_count,
Expand Down
32 changes: 31 additions & 1 deletion src/apps/desktop/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -547,9 +547,39 @@ pub async fn run() {
}

let app_handle = app.handle().clone();
let workspace_startup_bootstrap_snapshot = {
let app_state: tauri::State<'_, api::app_state::AppState> = app.state();
let startup_trace_state: tauri::State<'_, startup_trace::DesktopStartupTrace> =
app.state();
tokio::task::block_in_place(|| {
tokio::runtime::Handle::current().block_on(
prepare_workspace_startup_bootstrap_snapshot(
&app_state,
&app_handle,
&startup_trace_state,
),
)
})
.and_then(|snapshot| {
serde_json::to_value(snapshot)
.map_err(|error| {
log::warn!(
"Failed to serialize workspace startup bootstrap snapshot, frontend will fall back to startup command: {}",
error
);
error
})
.ok()
})
};
let window_started = Instant::now();
startup_trace.record_phase("main_window_create_start", "native_window");
theme::create_main_window(&app_handle, &startup_trace_id, &startup_trace);
theme::create_main_window(
&app_handle,
&startup_trace_id,
&startup_trace,
workspace_startup_bootstrap_snapshot,
);
let window_duration_ms = elapsed_ms(window_started);
startup_trace.record_step(
"native_step_end",
Expand Down
17 changes: 16 additions & 1 deletion src/apps/desktop/src/theme.rs
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,7 @@ struct StartupBootstrapConfig {
}

const MAX_BOOTSTRAP_KEYBINDINGS_JSON_BYTES: usize = 64 * 1024;
const MAX_BOOTSTRAP_WORKSPACE_STATE_JSON_BYTES: usize = 64 * 1024;

impl Default for ThemeConfig {
fn default() -> Self {
Expand Down Expand Up @@ -455,6 +456,7 @@ impl ThemeConfig {
&self,
startup_trace_id: &str,
bootstrap_config: &StartupBootstrapConfig,
workspace_startup_state: Option<&serde_json::Value>,
) -> String {
let theme_type = if self.is_light { "light" } else { "dark" };
let startup_locale = &bootstrap_config.locale;
Expand Down Expand Up @@ -483,6 +485,11 @@ impl ThemeConfig {
.filter(|json| json.len() <= MAX_BOOTSTRAP_KEYBINDINGS_JSON_BYTES)
.map(|json| format!("window.__BITFUN_BOOTSTRAP_KEYBINDINGS__ = {json};"))
.unwrap_or_default();
let bootstrap_workspace_startup_state_assignment = workspace_startup_state
.and_then(|state| serde_json::to_string(state).ok())
.filter(|json| json.len() <= MAX_BOOTSTRAP_WORKSPACE_STATE_JSON_BYTES)
.map(|json| format!("window.__BITFUN_BOOTSTRAP_WORKSPACE_STARTUP_STATE__ = {json};"))
.unwrap_or_default();

format!(
r#"
Expand All @@ -496,6 +503,7 @@ impl ThemeConfig {
window.__BITFUN_BOOTSTRAP_THEME_ID__ = {bootstrap_theme_id_json};
window.__BITFUN_BOOTSTRAP_THEME_SELECTION__ = {bootstrap_theme_selection_json};
{bootstrap_keybindings_assignment}
{bootstrap_workspace_startup_state_assignment}
function applyTheme() {{
var root = document.documentElement;
if (!root) return false;
Expand Down Expand Up @@ -545,6 +553,8 @@ impl ThemeConfig {
startup_messages_json = startup_messages_json,
show_startup_window_controls = show_startup_window_controls,
bootstrap_keybindings_assignment = bootstrap_keybindings_assignment,
bootstrap_workspace_startup_state_assignment =
bootstrap_workspace_startup_state_assignment,
)
}

Expand All @@ -561,12 +571,17 @@ pub fn create_main_window(
app_handle: &tauri::AppHandle,
startup_trace_id: &str,
startup_trace: &DesktopStartupTrace,
workspace_startup_state: Option<serde_json::Value>,
) {
let total_started_at = Instant::now();
let bootstrap_config = ThemeConfig::load_startup_bootstrap_config();
let theme = bootstrap_config.theme.clone();
let bg_color = theme.to_tauri_color();
let init_script = theme.generate_init_script(startup_trace_id, &bootstrap_config);
let init_script = theme.generate_init_script(
startup_trace_id,
&bootstrap_config,
workspace_startup_state.as_ref(),
);
startup_trace.record_step(
"native_step_end",
"native_window",
Expand Down
35 changes: 35 additions & 0 deletions src/web-ui/src/app/startup/startupPerformanceContract.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,41 @@ describe('startup performance contract', () => {
expect(configManagerSource).toContain('delete globalThis.__BITFUN_BOOTSTRAP_KEYBINDINGS__');
});

it('keeps workspace startup state on the bootstrap path with command fallback', () => {
const globalStateSource = readSource('../../shared/types/global-state.ts');
const desktopThemeSource = readSource('../../../../apps/desktop/src/theme.rs');
const desktopLibSource = readSource('../../../../apps/desktop/src/lib.rs');
const desktopCommandsSource = readSource('../../../../apps/desktop/src/api/commands.rs');

expect(desktopThemeSource).toContain('__BITFUN_BOOTSTRAP_WORKSPACE_STARTUP_STATE__');
expect(desktopThemeSource).toContain('MAX_BOOTSTRAP_WORKSPACE_STATE_JSON_BYTES');
expect(desktopLibSource).toContain('prepare_workspace_startup_bootstrap_snapshot');
expect(desktopLibSource).toContain('tokio::task::block_in_place');
expect(desktopLibSource).not.toContain('tauri::async_runtime::block_on(prepare_workspace_startup_bootstrap_snapshot');
expect(desktopCommandsSource).toContain('initialize_workspace_startup_state_impl');
expect(globalStateSource).toContain('consumeBootstrapWorkspaceStartupStateSnapshot');
expect(globalStateSource).toContain('__BITFUN_BOOTSTRAP_WORKSPACE_STARTUP_STATE__');
expect(globalStateSource).toContain(
'delete globalThis.__BITFUN_BOOTSTRAP_WORKSPACE_STARTUP_STATE__'
);
});

it('keeps startup resource timing as bounded E2E-only report data', () => {
const perfSpecSource = readSource(
'../../../../../tests/e2e/specs/performance/startup-session-perf.spec.ts'
);
const resourceTimingSource = readSource(
'../../../../../tests/e2e/helpers/performance-resource-timing.ts'
);

expect(perfSpecSource).toContain('readStartupResourceTimingSummary');
expect(perfSpecSource).toContain('resourceTiming');
expect(resourceTimingSource).toContain('sanitizeResourceTimingName');
expect(resourceTimingSource).toContain('MAX_RESOURCE_TIMING_ENTRIES');
expect(resourceTimingSource).not.toContain('console.log');
expect(resourceTimingSource).not.toContain('createLogger');
});

it('keeps built-in theme startup on the bootstrap path without pre-render config writes', () => {
const mainSource = readSource('../../main.tsx');
const themeServiceSource = readSource('../../infrastructure/theme/core/ThemeService.ts');
Expand Down
118 changes: 118 additions & 0 deletions src/web-ui/src/shared/types/global-state.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';

import type { WorkspaceStartupStateSnapshot } from '@/infrastructure/api/service-api/GlobalAPI';
import { createGlobalStateAPI, WorkspaceKind, WorkspaceType } from './global-state';

const globalApiMocks = vi.hoisted(() => ({
initializeWorkspaceStartupState: vi.fn(),
}));

vi.mock('@/infrastructure/api', () => ({
globalAPI: globalApiMocks,
workspaceAPI: {},
}));

vi.mock('../utils/logger', () => ({
createLogger: () => ({
debug: vi.fn(),
info: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
}),
}));

type BootstrapGlobals = typeof globalThis & {
__BITFUN_BOOTSTRAP_WORKSPACE_STARTUP_STATE__?: unknown;
};

const bootstrapGlobals = globalThis as BootstrapGlobals;

function createWorkspaceSnapshot(): WorkspaceStartupStateSnapshot {
const workspace = {
id: 'workspace-1',
name: 'Workspace 1',
rootPath: 'D:/workspace/project',
workspaceType: 'singleProject',
workspaceKind: 'normal',
languages: ['TypeScript'],
openedAt: '2026-06-18T00:00:00.000Z',
lastAccessed: '2026-06-18T00:00:00.000Z',
tags: [],
relatedPaths: [{ path: 'D:/workspace/project/docs', description: null }],
};

return {
cleanupRemovedCount: 1,
currentWorkspace: workspace,
recentWorkspaces: [workspace],
openedWorkspaces: [workspace],
legacyRemoteWorkspace: {
connectionId: 'conn-1',
connectionName: 'Remote',
remotePath: '/repo',
sshHost: 'devbox',
},
};
}

describe('createGlobalStateAPI workspace startup bootstrap', () => {
beforeEach(() => {
vi.clearAllMocks();
delete bootstrapGlobals.__BITFUN_BOOTSTRAP_WORKSPACE_STARTUP_STATE__;
});

it('uses the injected startup workspace snapshot once without a startup IPC', async () => {
bootstrapGlobals.__BITFUN_BOOTSTRAP_WORKSPACE_STARTUP_STATE__ = createWorkspaceSnapshot();
globalApiMocks.initializeWorkspaceStartupState.mockResolvedValue({
cleanupRemovedCount: 0,
currentWorkspace: null,
recentWorkspaces: [],
openedWorkspaces: [],
legacyRemoteWorkspace: null,
});

const api = createGlobalStateAPI();
const state = await api.initializeWorkspaceStartupState();

expect(globalApiMocks.initializeWorkspaceStartupState).not.toHaveBeenCalled();
expect(state.cleanupRemovedCount).toBe(1);
expect(state.currentWorkspace?.workspaceType).toBe(WorkspaceType.SingleProject);
expect(state.currentWorkspace?.workspaceKind).toBe(WorkspaceKind.Normal);
expect(state.currentWorkspace?.sshHost).toBe('localhost');
expect(state.currentWorkspace?.relatedPaths).toEqual([
{ path: 'D:/workspace/project/docs', description: undefined },
]);
expect(state.legacyRemoteWorkspace).toEqual({
connectionId: 'conn-1',
connectionName: 'Remote',
remotePath: '/repo',
sshHost: 'devbox',
});
expect(
Object.prototype.hasOwnProperty.call(
bootstrapGlobals,
'__BITFUN_BOOTSTRAP_WORKSPACE_STARTUP_STATE__'
)
).toBe(false);

await api.initializeWorkspaceStartupState();
expect(globalApiMocks.initializeWorkspaceStartupState).toHaveBeenCalledTimes(1);
});

it('falls back to the startup command when the bootstrap snapshot is invalid', async () => {
bootstrapGlobals.__BITFUN_BOOTSTRAP_WORKSPACE_STARTUP_STATE__ = { recentWorkspaces: [] };
globalApiMocks.initializeWorkspaceStartupState.mockResolvedValue(createWorkspaceSnapshot());

const api = createGlobalStateAPI();
const state = await api.initializeWorkspaceStartupState();

expect(globalApiMocks.initializeWorkspaceStartupState).toHaveBeenCalledTimes(1);
expect(state.currentWorkspace?.id).toBe('workspace-1');
expect(
Object.prototype.hasOwnProperty.call(
bootstrapGlobals,
'__BITFUN_BOOTSTRAP_WORKSPACE_STARTUP_STATE__'
)
).toBe(false);
});
});
Loading
Loading