Skip to content

feat(icon): implement asynchronous icon loading to improve startup pe… - #1509

Merged
zerob13 merged 1 commit into
devfrom
icon-loaded
Apr 22, 2026
Merged

feat(icon): implement asynchronous icon loading to improve startup pe…#1509
zerob13 merged 1 commit into
devfrom
icon-loaded

Conversation

@zhangmo8

@zhangmo8 zhangmo8 commented Apr 22, 2026

Copy link
Copy Markdown
Collaborator

…rformance

Summary by CodeRabbit

Release Notes

  • Performance

    • Improved application startup time through asynchronous icon loading instead of synchronous initialization.
    • Icons now preload in the background after app mount, reducing initial load delays.
  • Reliability

    • Enhanced error handling and logging for icon loading failures.
    • Prevents repeated loading attempts when errors occur.

@coderabbitai

coderabbitai Bot commented Apr 22, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

These changes refactor icon loading from synchronous registration at app startup to asynchronous on-demand loading via a new iconLoader module. The module maintains shared state to ensure icons load only once, with concurrent preloading triggered after app mount via setTimeout.

Changes

Cohort / File(s) Summary
Icon Loader Module
src/renderer/src/lib/iconLoader.ts
New module that manages on-demand icon collection loading with shared load state (isLoading, isLoaded, loadPromise) to prevent duplicate imports. Exports ensureIconsLoaded() for guaranteed load and preloadIcons() for fire-and-forget preload. Dynamically imports @iconify/vue and icon JSON datasets with runtime type checking and error logging.
Settings App Icon Loading
src/renderer/settings/App.vue, src/renderer/settings/main.ts
Removed synchronous addCollection() calls for icon registration. Added ensureIconsLoaded() invocation in settings App's onMounted hook and async preloadIcons() triggered via setTimeout(..., 0) after mount in settings main.ts with error handling.
Main App Icon Loading
src/renderer/src/App.vue, src/renderer/src/main.ts
Removed synchronous icon registration. Added ensureIconsLoaded() call on onMounted and concurrent initialization pattern where initAppStores() and sessionStore.fetchSessions() are invoked without awaiting. Added async preloadIcons() after mount with error logging in main.ts.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested reviewers

  • zerob13

Poem

🐰 Icons once eager, now patient they wait,
Loaded on-demand, avoiding startup's weight,
A rabbit's design: defer, don't delay,
Let apps mount quick and fetch icons their way!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: implementing asynchronous icon loading to improve startup performance, which aligns with the substantial refactoring across multiple files to move icon registration from synchronous startup to async post-mount loading.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch icon-loaded

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
src/renderer/src/main.ts (1)

71-77: Same redundancy as settings/main.ts.

preloadIcons() here and ensureIconsLoaded() in src/renderer/src/App.vue's onMounted both schedule the same work post-mount. Dedup via state.loadPromise makes this safe, but you can keep just one call site for clarity. If you intend this to start loading as early as possible (before App.vue mounts its hook), consider calling preloadIcons() directly (without setTimeout) right after app.mount('#app') — the setTimeout(…, 0) only defers by a macrotask and provides no measurable benefit over the direct call since dynamic import() is already async.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/src/main.ts` around lines 71 - 77, Both preloadIcons() in
main.ts and ensureIconsLoaded() in App.vue schedule the same post-mount icon
loading (deduped by state.loadPromise), so remove the redundant setTimeout call:
either call preloadIcons() directly right after app.mount('#app') to start
loading ASAP or delete this call and rely on ensureIconsLoaded() in App.vue;
update references to preloadIcons, ensureIconsLoaded, and state.loadPromise
accordingly so only one callsite initiates the load and no setTimeout(..., 0) is
used.
src/renderer/settings/main.ts (1)

75-81: Post-mount setTimeout(preloadIcons, 0) is redundant with ensureIconsLoaded() in App.vue's onMounted.

Both fire after mount and are deduped via the shared loadPromise, so this is harmless — just noting it so you can drop one if you prefer a single entry point. Keeping the one in App.vue ensures loading is tied to the component lifecycle; the setTimeout here adds nothing beyond yielding a macrotask.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/settings/main.ts` around lines 75 - 81, Redundant post-mount
scheduling: remove the setTimeout wrapper that calls preloadIcons() in main.ts
because App.vue already triggers icon loading via ensureIconsLoaded() in its
onMounted; locate the setTimeout(() => { preloadIcons().catch(...) }, 0) block
in src/renderer/settings/main.ts (the preloadIcons function and
ensureIconsLoaded in App.vue are the shared load entry points) and delete that
setTimeout block so icon loading is only invoked from the component lifecycle
(or alternatively keep the main.ts call and remove the ensureIconsLoaded() call
in App.vue—pick one consistent single entry point).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/renderer/src/App.vue`:
- Around line 389-400: The App currently calls ensureIconsLoaded() without
awaiting it in onMounted, causing AppBar/WindowSideBar icons to briefly render
empty; update the startup flow so ensureIconsLoaded() completes before
icon-heavy chrome is rendered — either await ensureIconsLoaded() in onMounted or
introduce an iconsReady reactive flag (set when ensureIconsLoaded resolves) and
gate AppBar/WindowSideBar render with that flag (leave RouterView gating via
isStartupRouteReady as-is); refer to ensureIconsLoaded, onMounted, AppBar,
WindowSideBar, RouterView, isStartupRouteReady, and the
addCollection/preloadIcons logic in iconLoader.ts when making the change.

In `@src/renderer/src/lib/iconLoader.ts`:
- Around line 58-64: The catch block in ensureIconsLoaded currently sets
state.isLoaded = true which prevents future retries; change the error handling
so that on failure you keep state.isLoaded = false, clear state.loadPromise (so
subsequent callers can attempt a retry), still log the error and do not rethrow,
and ensure state.isLoading is set to false in the finally block; refer to
ensureIconsLoaded, state.isLoaded, state.isLoading, and state.loadPromise when
making this change.

---

Nitpick comments:
In `@src/renderer/settings/main.ts`:
- Around line 75-81: Redundant post-mount scheduling: remove the setTimeout
wrapper that calls preloadIcons() in main.ts because App.vue already triggers
icon loading via ensureIconsLoaded() in its onMounted; locate the setTimeout(()
=> { preloadIcons().catch(...) }, 0) block in src/renderer/settings/main.ts (the
preloadIcons function and ensureIconsLoaded in App.vue are the shared load entry
points) and delete that setTimeout block so icon loading is only invoked from
the component lifecycle (or alternatively keep the main.ts call and remove the
ensureIconsLoaded() call in App.vue—pick one consistent single entry point).

In `@src/renderer/src/main.ts`:
- Around line 71-77: Both preloadIcons() in main.ts and ensureIconsLoaded() in
App.vue schedule the same post-mount icon loading (deduped by
state.loadPromise), so remove the redundant setTimeout call: either call
preloadIcons() directly right after app.mount('#app') to start loading ASAP or
delete this call and rely on ensureIconsLoaded() in App.vue; update references
to preloadIcons, ensureIconsLoaded, and state.loadPromise accordingly so only
one callsite initiates the load and no setTimeout(..., 0) is used.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e38d520f-0618-460d-8f0a-5b4da1120f8b

📥 Commits

Reviewing files that changed from the base of the PR and between 1ecba2d and 74f4140.

📒 Files selected for processing (5)
  • src/renderer/settings/App.vue
  • src/renderer/settings/main.ts
  • src/renderer/src/App.vue
  • src/renderer/src/lib/iconLoader.ts
  • src/renderer/src/main.ts

Comment thread src/renderer/src/App.vue
Comment on lines 389 to 400
onMounted(() => {
window.addEventListener('keydown', handleEscKey)

// initialize store data
// Ensure icons are loaded (load asynchronously, can happen in parallel with store init)
void ensureIconsLoaded()

// Start all critical data loads in parallel, don't wait for them
// This way session data starts loading much earlier instead of waiting for initAppStores() to complete
void initAppStores()
void sessionStore.fetchSessions()
setupMcpDeeplink()
setupAppIpcRuntime()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Icon flicker risk on first paint.

RouterView is gated by isStartupRouteReady, but AppBar / WindowSideBar render immediately and likely contain @iconify/vue Icon usages. Because ensureIconsLoaded() is not awaited and the JSON collections are dynamically imported, there will be a short window where those icons render blank until the chunks land and addCollection() runs. Not a correctness bug, but worth validating visually on a cold load — if noticeable, gate icon-heavy chrome on the resolved promise or inline a minimal critical icon set.

Otherwise the parallelization (initAppStores + fetchSessions + ensureIconsLoaded not awaited) looks fine; the existing dedup inside iconLoader.ts handles the duplicate call from main.ts's setTimeout(preloadIcons, 0) safely.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/src/App.vue` around lines 389 - 400, The App currently calls
ensureIconsLoaded() without awaiting it in onMounted, causing
AppBar/WindowSideBar icons to briefly render empty; update the startup flow so
ensureIconsLoaded() completes before icon-heavy chrome is rendered — either
await ensureIconsLoaded() in onMounted or introduce an iconsReady reactive flag
(set when ensureIconsLoaded resolves) and gate AppBar/WindowSideBar render with
that flag (leave RouterView gating via isStartupRouteReady as-is); refer to
ensureIconsLoaded, onMounted, AppBar, WindowSideBar, RouterView,
isStartupRouteReady, and the addCollection/preloadIcons logic in iconLoader.ts
when making the change.

Comment on lines +58 to +64
} catch (error) {
console.error('[Startup][Renderer] Failed to load icons:', error)
// 继续执行,不要因为 icon 加载失败而中断应用
state.isLoaded = true
} finally {
state.isLoading = false
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Error path marks isLoaded = true, preventing any future retry.

On failure, both isLoaded is set to true and isLoading is cleared. Subsequent calls to ensureIconsLoaded() will short-circuit on line 28–30 and never retry, so a transient failure (e.g., chunk load error on a flaky network) permanently leaves the app icon-less for that session. Consider leaving isLoaded = false on error and clearing state.loadPromise so the next caller can retry, while still not throwing out of ensureIconsLoaded to avoid blocking the app.

🛡️ Suggested change
     } catch (error) {
       console.error('[Startup][Renderer] Failed to load icons:', error)
-      // 继续执行,不要因为 icon 加载失败而中断应用
-      state.isLoaded = true
+      // Allow a future caller to retry instead of permanently marking as loaded.
+      state.loadPromise = null
     } finally {
       state.isLoading = false
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
} catch (error) {
console.error('[Startup][Renderer] Failed to load icons:', error)
// 继续执行,不要因为 icon 加载失败而中断应用
state.isLoaded = true
} finally {
state.isLoading = false
}
} catch (error) {
console.error('[Startup][Renderer] Failed to load icons:', error)
// Allow a future caller to retry instead of permanently marking as loaded.
state.loadPromise = null
} finally {
state.isLoading = false
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/src/lib/iconLoader.ts` around lines 58 - 64, The catch block in
ensureIconsLoaded currently sets state.isLoaded = true which prevents future
retries; change the error handling so that on failure you keep state.isLoaded =
false, clear state.loadPromise (so subsequent callers can attempt a retry),
still log the error and do not rethrow, and ensure state.isLoading is set to
false in the finally block; refer to ensureIconsLoaded, state.isLoaded,
state.isLoading, and state.loadPromise when making this change.

@zerob13

zerob13 commented Apr 22, 2026

Copy link
Copy Markdown
Collaborator

I found three actionable issues in this PR:

  1. [P1] Register icon collections before child components mount
    ensureIconsLoaded() now runs from onMounted() in src/renderer/src/App.vue, so WindowSideBar and the settings navigation render <Icon> before the local lucide / vscode-icons collections are registered. In that state @iconify/vue starts a remote loadIcons() request, and a later addCollection() does not wake that pending load, so offline or firewalled installs can show blank chrome icons until the request times out. The same startup pattern appears in src/renderer/settings/App.vue.

  2. [P2] Allow icon loading to retry after transient import errors
    In src/renderer/src/lib/iconLoader.ts, the catch branch still sets state.isLoaded = true. If the first dynamic import fails during HMR or just after an auto-update, every later ensureIconsLoaded() / preloadIcons() call will short-circuit, which leaves icons unavailable for the rest of that renderer session.

  3. [P2] Avoid fetching the session list twice during startup
    src/renderer/src/App.vue now calls sessionStore.fetchSessions() during onMounted(), while ChatTabView already does await sessionStore.fetchSessions() in its startup path. Because / always redirects into ChatTabView, this guarantees a second full listLightweight IPC request on launch, and the request-id guard can discard the earlier result, turning the new parallel fetch into pure extra startup work.

@zerob13
zerob13 merged commit 16a157e into dev Apr 22, 2026
3 checks passed
@zhangmo8
zhangmo8 deleted the icon-loaded branch April 24, 2026 08:51
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.

2 participants