Current Behavior
Switching tabs quickly (clicking through 3+ tabs, or holding the switch-tab shortcut) intermittently freezes the UI for 1.3–1.6 seconds. Single, slow-paced switches feel fine; the stall appears once switches come in quick succession, and it gets worse with more tabs/blocks in the workspace. Observed on Windows; the mechanism below is platform-independent, but slower disk I/O (NTFS + Defender) makes it much more visible there.
Expected Behavior
Tab switching stays in the low-milliseconds range regardless of how fast you switch or how many tabs the workspace has.
Steps To Reproduce
- Windows 11, Wave with one workspace containing ~5+ tabs, each with a few terminal blocks
- Rapidly switch between tabs (click tabs in quick succession or repeat the tab-switch keybinding)
- UI stalls for over a second; profiling the backend shows
SetActiveTab spiking to ~1.5s
Wave Version
v0.14.5 (also reproduced on recent main)
Platform
Windows
OS Version/Distribution
Windows 11
Architecture
x64
Anything else?
I profiled this and the cost is not the WebContentsView swap (setBounds is ~0.1ms) — it's a flood of DB operations triggered on every switch, all serialized on the single-connection store:
reinitWave reloads everything on each switch: client, window, workspace, the switched-to tab, its layout, plus every other tab in the workspace — N+5 DB reads per switch. These are almost entirely redundant: the tab's webContents stays alive off-screen with live WPS subscriptions, so its cache is already fresh.
WorkspaceService.SetActiveTab reads the tab and all of its blocks to build a return value that no caller consumes (the frontend signature is already Promise<void>).
Under rapid switching these reads queue up on the store and everything behind them waits.
Measured results after fixing both (reloading only the switched-to tab + layout as a safety net, and dropping the unused return-value reads):
SetActiveTab backend time: max 1576ms → ~20ms (zero spikes >100ms)
- Switch median: 16ms → 6.4ms
Working fix in my fork:
- freefrank/waveterm@9a8d0ee (the core change)
- freefrank/waveterm@71ea570 (follow-up: keeps the post-switch
globalRefocus(), and adds a throttled background resync of client/window/workspace/tabs so a websocket reconnect while a tab is hidden can't leave its cache permanently stale)
Two design points worth flagging if you want this upstreamed:
- The follow-up uses a 30s-throttled background resync as the stale-cache safety net; an alternative (arguably cleaner) is resync-on-reconnect in
wos.ts, which didn't exist as a hook when I wrote this.
- Removing
SetActiveTab's return value is a service-contract change; I verified the only caller discards it, but calling it out explicitly.
Happy to open a PR with the fix if this direction works for you.
Current Behavior
Switching tabs quickly (clicking through 3+ tabs, or holding the switch-tab shortcut) intermittently freezes the UI for 1.3–1.6 seconds. Single, slow-paced switches feel fine; the stall appears once switches come in quick succession, and it gets worse with more tabs/blocks in the workspace. Observed on Windows; the mechanism below is platform-independent, but slower disk I/O (NTFS + Defender) makes it much more visible there.
Expected Behavior
Tab switching stays in the low-milliseconds range regardless of how fast you switch or how many tabs the workspace has.
Steps To Reproduce
SetActiveTabspiking to ~1.5sWave Version
v0.14.5 (also reproduced on recent
main)Platform
Windows
OS Version/Distribution
Windows 11
Architecture
x64
Anything else?
I profiled this and the cost is not the WebContentsView swap (
setBoundsis ~0.1ms) — it's a flood of DB operations triggered on every switch, all serialized on the single-connection store:reinitWavereloads everything on each switch: client, window, workspace, the switched-to tab, its layout, plus every other tab in the workspace — N+5 DB reads per switch. These are almost entirely redundant: the tab's webContents stays alive off-screen with live WPS subscriptions, so its cache is already fresh.WorkspaceService.SetActiveTabreads the tab and all of its blocks to build a return value that no caller consumes (the frontend signature is alreadyPromise<void>).Under rapid switching these reads queue up on the store and everything behind them waits.
Measured results after fixing both (reloading only the switched-to tab + layout as a safety net, and dropping the unused return-value reads):
SetActiveTabbackend time: max 1576ms → ~20ms (zero spikes >100ms)Working fix in my fork:
globalRefocus(), and adds a throttled background resync of client/window/workspace/tabs so a websocket reconnect while a tab is hidden can't leave its cache permanently stale)Two design points worth flagging if you want this upstreamed:
wos.ts, which didn't exist as a hook when I wrote this.SetActiveTab's return value is a service-contract change; I verified the only caller discards it, but calling it out explicitly.Happy to open a PR with the fix if this direction works for you.