feat(splashscreen): enhance startup sequence and splashscreen handling

This update improves the splashscreen management during application startup. The splashscreen will now remain visible for a minimum duration and will only close after the main application is ready, ensuring a smoother user experience.

- Introduced a new startup sequence to manage background tasks.
- Added error handling for splashscreen closure to maintain functionality.
- Optimized the timing for displaying and hiding the splashscreen.
This commit is contained in:
Christoph Brandau
2026-07-08 18:25:43 +02:00
parent 7e7c1b0b69
commit a072bba2e6
3 changed files with 107 additions and 18 deletions
+13 -5
View File
@@ -22,14 +22,22 @@ use git::{
use tauri::Manager; use tauri::Manager;
#[tauri::command] #[tauri::command]
fn close_splashscreen(app: tauri::AppHandle) { fn close_splashscreen(app: tauri::AppHandle) -> Result<(), String> {
if let Some(window) = app.get_webview_window("splashscreen") {
let _ = window.hide();
window
.destroy()
.map_err(|error| format!("failed to destroy splashscreen: {error}"))?;
}
if let Some(window) = app.get_webview_window("main") { if let Some(window) = app.get_webview_window("main") {
let _ = window.show(); window
.show()
.map_err(|error| format!("failed to show main window: {error}"))?;
let _ = window.set_focus(); let _ = window.set_focus();
} }
if let Some(window) = app.get_webview_window("splashscreen") {
let _ = window.close(); Ok(())
}
} }
#[tokio::main] #[tokio::main]
+94 -8
View File
@@ -1,5 +1,6 @@
<script lang="ts"> <script lang="ts">
import { onDestroy, onMount, tick } from "svelte"; import { onDestroy, onMount, tick } from "svelte";
import { invoke } from "@tauri-apps/api/core";
import { open as openDialog } from "@tauri-apps/plugin-dialog"; import { open as openDialog } from "@tauri-apps/plugin-dialog";
import { check, type DownloadEvent, type Update } from "@tauri-apps/plugin-updater"; import { check, type DownloadEvent, type Update } from "@tauri-apps/plugin-updater";
import { AlertCircle, BookOpen, Cherry, Download, FolderOpen, GitBranch, GitMerge, LoaderCircle, Plus, Search, Star, X } from "@lucide/svelte"; import { AlertCircle, BookOpen, Cherry, Download, FolderOpen, GitBranch, GitMerge, LoaderCircle, Plus, Search, Star, X } from "@lucide/svelte";
@@ -268,6 +269,8 @@
const BACKGROUND_FETCH_INTERVAL = 180_000; const BACKGROUND_FETCH_INTERVAL = 180_000;
const BACKGROUND_FETCH_AFTER_SWITCH_GRACE_MS = 30_000; const BACKGROUND_FETCH_AFTER_SWITCH_GRACE_MS = 30_000;
const BACKGROUND_FETCH_OTHER_BATCH_SIZE = 2; const BACKGROUND_FETCH_OTHER_BATCH_SIZE = 2;
const STARTUP_SPLASH_MIN_VISIBLE_MS = 850;
const STARTUP_FETCH_MAX_WAIT_MS = 20_000;
let backgroundFetchTimer: ReturnType<typeof setInterval> | undefined; let backgroundFetchTimer: ReturnType<typeof setInterval> | undefined;
let backgroundRepoStatusTimer: ReturnType<typeof setInterval> | undefined; let backgroundRepoStatusTimer: ReturnType<typeof setInterval> | undefined;
let backgroundFetchInFlight = false; let backgroundFetchInFlight = false;
@@ -346,14 +349,7 @@
// ── Lifecycle ────────────────────────────────────────────────────────────── // ── Lifecycle ──────────────────────────────────────────────────────────────
onMount(() => { onMount(() => {
initAnalytics(); void runStartupSequence();
loadRepoLists();
autoRefreshTimer = setInterval(() => { void autoRefreshTick(); }, AUTO_REFRESH_INTERVAL);
backgroundRepoStatusTimer = setInterval(() => { void backgroundRepoStatusTick(false); }, BACKGROUND_REPO_STATUS_INTERVAL);
backgroundFetchTimer = setInterval(() => { void backgroundFetchTick(); }, BACKGROUND_FETCH_INTERVAL);
void backgroundRepoStatusTick(false);
void checkForUpdates();
void initCommitAi();
}); });
onDestroy(() => { onDestroy(() => {
@@ -368,6 +364,96 @@
if (activeFileHistoryRequestId) void cancelFileHistory(activeFileHistoryRequestId); if (activeFileHistoryRequestId) void cancelFileHistory(activeFileHistoryRequestId);
}); });
function wait(ms: number): Promise<void> {
return new Promise((resolve) => window.setTimeout(resolve, ms));
}
function waitForAnimationFrame(): Promise<void> {
return new Promise((resolve) => requestAnimationFrame(() => resolve()));
}
async function waitForStartupPaint() {
await tick();
await waitForAnimationFrame();
await waitForAnimationFrame();
}
async function closeStartupSplashscreen() {
try {
await invoke("close_splashscreen");
} catch {
// Browser preview and failed startup paths should keep working without Tauri.
}
}
function startBackgroundTimers() {
autoRefreshTimer = setInterval(() => { void autoRefreshTick(); }, AUTO_REFRESH_INTERVAL);
backgroundRepoStatusTimer = setInterval(() => { void backgroundRepoStatusTick(false); }, BACKGROUND_REPO_STATUS_INTERVAL);
backgroundFetchTimer = setInterval(() => { void backgroundFetchTick(); }, BACKGROUND_FETCH_INTERVAL);
}
async function runStartupSequence() {
const startupStartedAt = performance.now();
initAnalytics();
loadRepoLists();
void checkForUpdates();
void initCommitAi();
try {
await waitForStartupPaint();
await Promise.race([
fetchOpenRepositoriesDuringStartup(),
wait(STARTUP_FETCH_MAX_WAIT_MS),
]);
} finally {
const remainingSplashTime = STARTUP_SPLASH_MIN_VISIBLE_MS - (performance.now() - startupStartedAt);
if (remainingSplashTime > 0) await wait(remainingSplashTime);
await closeStartupSplashscreen();
startBackgroundTimers();
void backgroundRepoStatusTick(false);
}
}
async function fetchOpenRepositoriesDuringStartup() {
const paths = uniqueRepoPaths(repoTabs.map((tab) => tab.path));
if (paths.length === 0 || backgroundFetchInFlight) return;
let fetchedRepositories = 0;
let failedRepositories = 0;
backgroundFetchInFlight = true;
trackEvent("startup_open_repositories_fetch_started", {
open_repositories: paths.length,
});
try {
for (const path of paths) {
try {
const nextStatus = await fetchRemote(path);
if (sameRepoPath(path, activeRepoPath)) applyStatus(nextStatus);
else updateRepoManagementStatus(path, nextStatus);
fetchedRepositories += 1;
} catch {
failedRepositories += 1;
try {
updateRepoManagementStatus(path, await getStatus(path));
} catch {
// Keep the cached tab data if this repo is unavailable at startup.
}
}
}
} finally {
backgroundFetchInFlight = false;
trackEvent("startup_open_repositories_fetch_finished", {
open_repositories: paths.length,
fetched_repositories: fetchedRepositories,
failed_repositories: failedRepositories,
});
}
}
$: scheduleAutoHideError("errorMessage", errorMessage, (message) => { $: scheduleAutoHideError("errorMessage", errorMessage, (message) => {
if (errorMessage === message) errorMessage = ""; if (errorMessage === message) errorMessage = "";
}); });
-5
View File
@@ -1,5 +1,4 @@
import { mount } from "svelte"; import { mount } from "svelte";
import { invoke } from "@tauri-apps/api/core";
import App from "./App.svelte"; import App from "./App.svelte";
import "./app.css"; import "./app.css";
@@ -12,8 +11,4 @@ if (!target) {
const app = mount(App, { target }); const app = mount(App, { target });
void invoke("close_splashscreen").catch(() => {
// Browser preview and failed startup paths should keep working without Tauri.
});
export default app; export default app;