feat: lazy load stuffs which are not needed on startup#2366
feat: lazy load stuffs which are not needed on startup#2366bajrangCoder wants to merge 2 commits into
Conversation
Greptile SummaryThis PR defers heavy startup modules — TerminalManager, FileBrowser, Prettier, ads, plugins, and the welcome tab — behind dynamic
Confidence Score: 3/5Not safe to merge without addressing the incomplete welcome-tab type migration and the terminal-restore blocking issue. The welcome tab type was renamed from "page" to "welcome" but the context-menu innerHTML guard in main.js (line 839) was not updated, so the welcome tab will attempt to render editor-specific menu content. Terminal session restoration is now awaited before post-render settings and ad startup, introducing a new serial dependency that can slow perceived startup. The resumeHandler can also invoke handleResume() on an uninitialized adRewards instance if the device is backgrounded during the 500ms+rAF startup window. src/main.js has three distinct issues: the missed type guard at line 839, the terminal-restore blocking, and the adRewards init race. src/lib/acode.js needs the terminalTouchSelectionMoreOptions.list() method to handle the not-yet-loaded case consistently with its async siblings. Important Files Changed
Sequence Diagram%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant App
participant Splash
participant rAF as requestAnimationFrame
participant adRewards
participant Terminal
participant Plugins
App->>Splash: loadApp() + 500ms timeout
Splash->>Splash: remove loading/splash classes
Splash->>rAF: schedule initAdRewards()
Splash->>Terminal: await restoreTerminalSessions()
Note over Terminal: blocks applySettings, startAds, checkForAppUpdates
Terminal-->>Splash: resolved
Splash->>Plugins: setTimeout(0) → loadPlugins()
Splash->>Splash: applySettings.afterRender()
Splash->>adRewards: startAds() [fire-and-forget]
rAF->>adRewards: initAdRewards() → adRewards.init()
Note over adRewards: resumeHandler can race init()
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant App
participant Splash
participant rAF as requestAnimationFrame
participant adRewards
participant Terminal
participant Plugins
App->>Splash: loadApp() + 500ms timeout
Splash->>Splash: remove loading/splash classes
Splash->>rAF: schedule initAdRewards()
Splash->>Terminal: await restoreTerminalSessions()
Note over Terminal: blocks applySettings, startAds, checkForAppUpdates
Terminal-->>Splash: resolved
Splash->>Plugins: setTimeout(0) → loadPlugins()
Splash->>Splash: applySettings.afterRender()
Splash->>adRewards: startAds() [fire-and-forget]
rAF->>adRewards: initAdRewards() → adRewards.init()
Note over adRewards: resumeHandler can race init()
|
| async function resumeHandler() { | ||
| try { | ||
| const adRewards = await loadAdRewards(); | ||
| adRewards.handleResume(); | ||
| } catch (error) { | ||
| console.error("Failed to resume ad rewards:", error); | ||
| } |
There was a problem hiding this comment.
handleResume() can fire before adRewards.init()
initAdRewards() is now deferred through a requestAnimationFrame inside the 500 ms setTimeout. If the user backgrounds and restores the app during that startup window, resumeHandler calls loadAdRewards() and immediately invokes handleResume() before init() has ever been called on that module — while initAdRewards() will later call init() on the exact same cached singleton. Whether handleResume() is safe to call on an uninitialized instance depends entirely on the adRewards module; the old code guaranteed init() finished first.
| async function resumeHandler() { | |
| try { | |
| const adRewards = await loadAdRewards(); | |
| adRewards.handleResume(); | |
| } catch (error) { | |
| console.error("Failed to resume ad rewards:", error); | |
| } | |
| async function resumeHandler() { | |
| try { | |
| // Ensure init has completed before calling handleResume. | |
| await initAdRewards(); | |
| const adRewards = await loadAdRewards(); | |
| adRewards.handleResume(); | |
| } catch (error) { | |
| console.error("Failed to resume ad rewards:", error); | |
| } |
| await restoreTerminalSessions(); | ||
|
|
||
| setTimeout(async () => { | ||
| try { | ||
| await loadPlugins(); | ||
|
|
||
| setTimeout(() => { | ||
| // Ensure at least one sidebar app is active after all plugins are loaded. | ||
| sidebarApps.ensureActiveApp(); | ||
|
|
||
| // Re-emit events for active file after plugins are loaded. | ||
| const { activeFile } = editorManager; | ||
| if (activeFile?.uri) { | ||
| editorManager.emit("file-loaded", activeFile); | ||
| editorManager.emit("switch-file", activeFile); | ||
| } | ||
| }, 0); | ||
| } catch (error) { | ||
| window.log("error", "Failed to load plugins!"); | ||
| window.log("error", error); | ||
| toast("Failed to load plugins!"); | ||
| } | ||
| } catch (error) { | ||
| window.log("error", "Failed to load plugins!"); | ||
| window.log("error", error); | ||
| toast("Failed to load plugins!"); | ||
| } | ||
| }, 0); | ||
|
|
||
| applySettings.afterRender(); |
There was a problem hiding this comment.
Terminal restoration now serially blocks post-render init
await restoreTerminalSessions() sits between the splash-removal and applySettings.afterRender() / startAds() / checkForAppUpdates(). Previously restorePersistedSessions() was fire-and-forget. If persisted terminal sessions involve slow SSH/WebSocket reconnects, every subsequent init step (settings, ads, update checks) is delayed for the entire duration of that network operation. Consider not awaiting terminal restoration here, or scheduling it after the remaining post-render work.
| list: () => | ||
| getLoadedTerminalManager()?.getTouchSelectionMoreOptions() || [], | ||
| }; |
There was a problem hiding this comment.
terminalTouchSelectionMoreOptions.list() silently returns [] before terminal loads
The add and remove methods on this object are async and await the terminal manager, but list is synchronous and returns getLoadedTerminalManager()?.getTouchSelectionMoreOptions() || []. Any plugin that calls list() before the terminal manager has been lazy-loaded (e.g., to read existing options at startup) will receive an empty array instead of the real list. This is an API contract break relative to the previous always-synchronous TerminalManager.getTouchSelectionMoreOptions() call that always had the manager available.
| canShowAds() { | ||
| return Boolean(!config.HAS_PRO && adRewards.canShowAds()); | ||
| return Boolean(!config.HAS_PRO && getLoadedAdRewards()?.canShowAds()); | ||
| }, |
There was a problem hiding this comment.
canShowAds() returns false before adRewards is loaded
getLoadedAdRewards() returns undefined until initAdRewards() resolves (deferred by rAF inside 500 ms timeout). Any call to canShowAds() during that startup window — and therefore any call to showAd() or showInterstitialIfReady() — will silently return false/skip the ad, even for non-pro users. This is a subtle behavior change that may suppress early-lifecycle ad impressions.
No description provided.