Skip to content

feat: Improve app startup time#1144

Merged
e-fisher merged 5 commits intomainfrom
feat/remove-splashscreen
Apr 7, 2026
Merged

feat: Improve app startup time#1144
e-fisher merged 5 commits intomainfrom
feat/remove-splashscreen

Conversation

@e-fisher
Copy link
Copy Markdown
Collaborator

@e-fisher e-fisher commented Apr 2, 2026

Description

Remove the splash screen window and replace it with a lightweight inline loading indicator in index.html. Routes are now lazy-loaded with React.lazy, so the renderer starts faster by deferring heavy view imports.

What changed:

  • Removed BrowserWindow-based splash screen and all associated code (IPC handler, preload API, hook, SVG assets, HTML file)
  • Added a simple CSS spinner directly in index.html (supports dark mode) as the initial loading state
  • Lazy-loaded all route views (Home, Recorder, Generator, etc.) with a Suspense fallback spinner in the Layout
  • Main window now shows via ready-to-show instead of waiting for a renderer-to-main IPC round trip
  • Deep link replay moved from the SplashscreenClose IPC event to after loadURL resolves

Time from npm start to app ready state was cut from ~20s to ~10s on my machine.

How to Test

  1. Run npm start and verify the app loads with a brief spinner, then shows the home view
  2. Verify dark mode shows the correct spinner colors (launch with system dark mode)
  3. Navigate between views and confirm lazy-loaded routes show a brief loading fallback (or load instantly on fast machines)

I've also created a custom build to verify deep links work as expected - confirmed by clicking "Start recording" from new test page in GCk6

Checklist

  • I have performed a self-review of my code.
  • I have added tests for my changes.
  • I have commented on my code, particularly in hard-to-understand areas.

Related PR(s)/Issue(s)

Resolves #1068


Note

Medium Risk
Medium risk because it changes Electron startup/window-show timing, IPC deep-link delivery, and route loading behavior, which could impact navigation and protocol-link handling across platforms.

Overview
Improves perceived startup performance by removing the separate BrowserWindow splash screen and replacing it with a lightweight inline spinner in index.html (with dark-mode styling).

Defers renderer work by lazy-loading the main route views via React.lazy and wrapping the routed outlet in Suspense with a delayed spinner fallback.

Simplifies startup/IPC flow by deleting splashscreen IPC APIs/assets and showing the main window on ready-to-show; deep links are now replayed after the main window finishes loading and are delivered via the new app:navigate channel with buffering in preload to avoid missing early events.

Reviewed by Cursor Bugbot for commit 2ff3cd8. Bugbot is set up for automated code reviews on this repo. Configure here.

Comment thread src/main.ts Outdated

if (process.env.NODE_ENV === 'development') {
mainWindow.webContents.openDevTools({ mode: 'detach' })
mainWindow.webContents.openDevTools({ mode: 'right' })
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was my personal pain point - I would always close separate window and open sidebar, because it's just more comfortable for my debugging. If others prefer the detached window, I'll revert this change. Or perhaps we can make this remember personal preference.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we don't specify the mode it will remember the last setting you used.

Suggested change
mainWindow.webContents.openDevTools({ mode: 'right' })
mainWindow.webContents.openDevTools()

Everybody wins!

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's perfect! 🤩

Copy link
Copy Markdown
Contributor

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Double showWindow call on macOS dock activation
    • Removed the redundant showWindow call in the activate event handler since the window is already shown by the ready-to-show event handler.

Create PR

Or push these changes by commenting:

@cursor push 4d80d47564
Preview (4d80d47564)
diff --git a/src/main.ts b/src/main.ts
--- a/src/main.ts
+++ b/src/main.ts
@@ -175,8 +175,8 @@
   // On OS X it's common to re-create a window in the app when the
   // dock icon is clicked and there are no other windows open.
   if (BrowserWindow.getAllWindows().length === 0) {
-    const mainWindow = await createWindow()
-    showWindow(mainWindow)
+    await createWindow()
+    // Window is already shown by the 'ready-to-show' event handler
   }
 })

You can send follow-ups to this agent here.

Comment thread src/main.ts
@e-fisher
Copy link
Copy Markdown
Collaborator Author

e-fisher commented Apr 2, 2026

@cursor push 4d80d47

Co-authored-by: Edgar Fisher <e-fisher@users.noreply.github.com>

Applied via @cursor push command
@e-fisher e-fisher temporarily deployed to azure-trusted-signing April 2, 2026 12:17 — with GitHub Actions Inactive
@e-fisher e-fisher temporarily deployed to azure-trusted-signing April 2, 2026 12:17 — with GitHub Actions Inactive
@e-fisher e-fisher temporarily deployed to azure-trusted-signing April 2, 2026 12:17 — with GitHub Actions Inactive
@e-fisher e-fisher temporarily deployed to azure-trusted-signing April 2, 2026 12:17 — with GitHub Actions Inactive
@e-fisher
Copy link
Copy Markdown
Collaborator Author

e-fisher commented Apr 2, 2026

bugbot run

Copy link
Copy Markdown
Contributor

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Duplicated delayed spinner logic across two components
    • Created a new useDelayedVisibility hook that abstracts the delayed spinner logic and updated both components to use it.

Create PR

Or push these changes by commenting:

@cursor push 6f580056c9
Preview (6f580056c9)
diff --git a/src/components/Layout/Layout.tsx b/src/components/Layout/Layout.tsx
--- a/src/components/Layout/Layout.tsx
+++ b/src/components/Layout/Layout.tsx
@@ -2,28 +2,19 @@
 import { Box, Flex, IconButton, Spinner } from '@radix-ui/themes'
 import { Allotment } from 'allotment'
 import { PanelLeftOpenIcon } from 'lucide-react'
-import { Suspense, useEffect, useState } from 'react'
+import { Suspense, useEffect } from 'react'
 import { Outlet, useLocation } from 'react-router-dom'
 import { useLocalStorage } from 'react-use'
 
+import { useDelayedVisibility } from '@/hooks/useDelayedVisibility'
 import { useListenDeepLinks } from '@/hooks/useListenDeepLinks'
 
 import { ActivityBar } from './ActivityBar'
 import { Sidebar } from './Sidebar'
 
 function RouteLoadingFallback() {
-  const [showSpinner, setShowSpinner] = useState(false)
+  const showSpinner = useDelayedVisibility()
 
-  useEffect(() => {
-    const timeout = setTimeout(() => {
-      setShowSpinner(true)
-    }, 50)
-
-    return () => {
-      clearTimeout(timeout)
-    }
-  }, [])
-
   return (
     <Flex
       align="center"

diff --git a/src/components/Layout/View.tsx b/src/components/Layout/View.tsx
--- a/src/components/Layout/View.tsx
+++ b/src/components/Layout/View.tsx
@@ -1,23 +1,14 @@
 import { css } from '@emotion/react'
 import { Flex, Spinner } from '@radix-ui/themes'
-import { PropsWithChildren, ReactNode, useEffect, useState } from 'react'
+import { PropsWithChildren, ReactNode } from 'react'
 
+import { useDelayedVisibility } from '@/hooks/useDelayedVisibility'
+
 import { ViewHeading } from './ViewHeading'
 
 function LoadingSpinner() {
-  const [showSpinner, setShowSpinner] = useState(false)
+  const showSpinner = useDelayedVisibility()
 
-  useEffect(() => {
-    // Only show the spinner if loading takes more than 50ms to avoid flickering
-    const timeout = setTimeout(() => {
-      setShowSpinner(true)
-    }, 50)
-
-    return () => {
-      clearTimeout(timeout)
-    }
-  }, [])
-
   return (
     <Flex
       css={css`

diff --git a/src/hooks/useDelayedVisibility.ts b/src/hooks/useDelayedVisibility.ts
new file mode 100644
--- /dev/null
+++ b/src/hooks/useDelayedVisibility.ts
@@ -1,0 +1,24 @@
+import { useEffect, useState } from 'react'
+
+/**
+ * Hook that delays showing a component until a specified delay has passed.
+ * Useful for preventing flickering of loading states for quick operations.
+ * 
+ * @param delayMs The delay in milliseconds before showing the component
+ * @returns A boolean indicating whether the component should be visible
+ */
+export function useDelayedVisibility(delayMs = 50) {
+  const [isVisible, setIsVisible] = useState(false)
+
+  useEffect(() => {
+    const timeout = setTimeout(() => {
+      setIsVisible(true)
+    }, delayMs)
+
+    return () => {
+      clearTimeout(timeout)
+    }
+  }, [delayMs])
+
+  return isVisible
+}
\ No newline at end of file

You can send follow-ups to this agent here.

Comment thread src/components/Layout/Layout.tsx
@e-fisher
Copy link
Copy Markdown
Collaborator Author

e-fisher commented Apr 2, 2026

@cursor push 6f58005

…ty hook

Co-authored-by: Edgar Fisher <e-fisher@users.noreply.github.com>

Applied via @cursor push command
@e-fisher
Copy link
Copy Markdown
Collaborator Author

e-fisher commented Apr 3, 2026

bugbot run

Copy link
Copy Markdown
Contributor

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Unrelated devtools mode change bundled in feature PR
    • Reverted the DevTools mode from 'right' back to 'detach' to maintain consistency with the team's established development environment.

Create PR

Or push these changes by commenting:

@cursor push 0e8604b911
Preview (0e8604b911)
diff --git a/src/main.ts b/src/main.ts
--- a/src/main.ts
+++ b/src/main.ts
@@ -115,7 +115,7 @@
   replayPendingDeepLink()
 
   if (process.env.NODE_ENV === 'development') {
-    mainWindow.webContents.openDevTools({ mode: 'right' })
+    mainWindow.webContents.openDevTools({ mode: 'detach' })
   }
 
   mainWindow.on('closed', () =>

You can send follow-ups to this agent here.

Comment thread src/main.ts Outdated
@e-fisher e-fisher marked this pull request as ready for review April 3, 2026 05:58
@e-fisher e-fisher requested a review from a team as a code owner April 3, 2026 05:58
@allansson allansson temporarily deployed to azure-trusted-signing April 7, 2026 06:39 — with GitHub Actions Inactive
@allansson allansson temporarily deployed to azure-trusted-signing April 7, 2026 06:39 — with GitHub Actions Inactive
@allansson allansson temporarily deployed to azure-trusted-signing April 7, 2026 06:39 — with GitHub Actions Inactive
@allansson allansson temporarily deployed to azure-trusted-signing April 7, 2026 06:39 — with GitHub Actions Inactive
allansson
allansson previously approved these changes Apr 7, 2026
Copy link
Copy Markdown
Collaborator

@allansson allansson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Really snappy startup now. ❤️

I would like to update the title of the PR to e.g. "feat: Improved startup time". I think it better captures why a user should be excited about the change.

I build a test version of the app and tested on Apple Silicon. Everything worked fine for me.

Comment thread src/handlers/app/preload.ts Outdated
let pendingDeepLink: string | null = null
let deepLinkCallback: ((url: string) => void) | null = null

ipcRenderer.on(AppHandler.DeepLink, (_, url: string) => {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't a change request, but more like a bit of documentation.

When doing my workspace hackathon I needed to update the frontend route from the backend. Cursor chose to reuse the DeepLink handler to do so.

So this is actually more of a ChangeRoute handler. 🤔

Comment thread src/main.ts Outdated

if (process.env.NODE_ENV === 'development') {
mainWindow.webContents.openDevTools({ mode: 'detach' })
mainWindow.webContents.openDevTools({ mode: 'right' })
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we don't specify the mode it will remember the last setting you used.

Suggested change
mainWindow.webContents.openDevTools({ mode: 'right' })
mainWindow.webContents.openDevTools()

Everybody wins!

@e-fisher e-fisher changed the title feat: Remove splashscreen, lazy load routes feat: Improve app startup time Apr 7, 2026
Copy link
Copy Markdown
Collaborator

@allansson allansson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

Copy link
Copy Markdown
Collaborator

@going-confetti going-confetti left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great improvement 🚢
Also closes #1068

@e-fisher e-fisher merged commit 7e122fe into main Apr 7, 2026
14 checks passed
@e-fisher e-fisher deleted the feat/remove-splashscreen branch April 7, 2026 10:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Improve k6 Studio startup time

4 participants