Skip to content

update emain-window to do repositioning in a simpler way #3087

Merged
sawka merged 2 commits intomainfrom
sawka/simpler-tabs
Mar 20, 2026
Merged

update emain-window to do repositioning in a simpler way #3087
sawka merged 2 commits intomainfrom
sawka/simpler-tabs

Conversation

@sawka
Copy link
Member

@sawka sawka commented Mar 19, 2026

the old way doesn't buy us anything, so we should just do it in a straightforward way.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 19, 2026

Walkthrough

This PR updates two files. In electron.vite.config.ts the Vite dev-server watch ignore pattern changes from "emain/**" to "**/emain/**", broadening ignored matches to any nested emain directory. In emain/emain-window.ts: several refactors—some let variables become const; tab initialization now sets tabView bounds immediately after tabView.initPromise completes; parallel reposition/initialization logic and repositionTabsSlowly were removed in favor of sequential initialization where new tabs await initializeTab(...) then call finalizePositioning(), and reused tabs send "wave-init" then call finalizePositioning(); removeTabViewLater uses the passed delayMs; and two unused parameters/variables were renamed with leading underscores.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: simplifying the repositioning logic in emain-window by removing the complex parallel pattern and replacing it with sequential behavior.
Description check ✅ Passed The description is directly related to the changeset, explaining the rationale for simplifying the repositioning approach in emain-window.

✏️ 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 sawka/simpler-tabs

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.

const workspaceList = await WorkspaceService.ListWorkspaces();

const workspaceHasWindow = !!workspaceList.find((wse) => wse.workspaceid === workspaceId)?.windowid;
const _workspaceHasWindow = !!workspaceList.find((wse) => wse.workspaceid === workspaceId)?.windowid;
Copy link
Contributor

Choose a reason for hiding this comment

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

WARNING: Unused variable - _workspaceHasWindow is computed but never used in the function. The variable was previously named workspaceHasWindow (without underscore prefix), suggesting it may have had usage that was accidentally removed. Either remove this variable or restore its intended usage.

@kilo-code-bot
Copy link
Contributor

kilo-code-bot bot commented Mar 19, 2026

Code Review Summary

Status: 1 Issue Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 0
Issue Details (click to expand)

WARNING

File Line Issue
emain/emain-window.ts 792 Unused variable _workspaceHasWindow - computed but never used in the function
Files Reviewed (2 files)
  • electron.vite.config.ts - glob pattern fix for watch ignore
  • emain/emain-window.ts - 1 issue

The PR simplifies tab repositioning by replacing async repositionTabsSlowly with synchronous finalizePositioning(). Bug fixes include correctly using the delayMs parameter in removeTabViewLater (was hardcoded to 1000). The main issue found is an unused variable in the delete-workspace handler.


Reviewed by minimax-m2.5-20260211 · 1,277,254 tokens

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
emain/emain-window.ts (1)

483-510: ⚠️ Potential issue | 🟡 Minor

Remove the unused repositionTabsSlowly method (lines 483-510).

This method is no longer called anywhere in the codebase and has become dead code from the refactoring. It should be deleted.

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

In `@emain/emain-window.ts` around lines 483 - 510, Delete the dead private method
repositionTabsSlowly(delayMs: number) from the class: remove its entire
implementation (the code referencing activeTabView, getContentBounds(),
delay(...) and calling finalizePositioning()) since it is no longer referenced
anywhere; ensure there are no remaining references to repositionTabsSlowly and
run a quick compile to confirm no usages remain.
🧹 Nitpick comments (3)
emain/emain-window.ts (3)

465-466: Misleading await on webContents.send() which returns void.

webContents.send() is a fire-and-forget IPC call that returns void, not a Promise. The await here does nothing and may mislead readers into thinking it waits for the message to be processed.

This creates an asymmetry: new tabs wait for waveReadyPromise before finalizePositioning(), but reused tabs call finalizePositioning() immediately. If this is intentional (reused tabs are already ready), consider removing the await for clarity.

♻️ Suggested fix
        } else {
            console.log("reusing an existing tab, calling wave-init", tabView.waveTabId);
-            await tabView.webContents.send("wave-init", tabView.savedInitOpts); // reinit
+            tabView.webContents.send("wave-init", tabView.savedInitOpts); // reinit
            this.finalizePositioning();
        }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@emain/emain-window.ts` around lines 465 - 466, The await on
tabView.webContents.send("wave-init", ...) is misleading because
webContents.send returns void; either remove the await to make the
fire-and-forget behavior explicit, or (if you intended to wait for the renderer
to be ready like for new tabs) replace the await on send with awaiting the
renderer-ready promise used elsewhere (e.g., await tabView.waveReadyPromise or
whatever waveReadyPromise is named) before calling this.finalizePositioning();
locate the send call and finalizePositioning() in emain-window.ts and apply one
of these two fixes so the code’s behavior and intent are consistent.

546-550: Parameter _delayMs is unused and the function uses a hardcoded value.

The parameter is marked unused but the timeout always uses 1000 regardless of what would have been passed. If the parameter was intended to be used, consider:

♻️ Option: Use the parameter or remove it from signature
-    private removeTabViewLater(tabId: string, _delayMs: number) {
+    private removeTabViewLater(tabId: string, delayMs: number) {
         setTimeout(() => {
             this.removeTabView(tabId, false);
-        }, 1000);
+        }, delayMs);
     }

Or if 1000ms is intentional, remove the parameter:

-    private removeTabViewLater(tabId: string, _delayMs: number) {
+    private removeTabViewLater(tabId: string) {
         setTimeout(() => {
             this.removeTabView(tabId, false);
         }, 1000);
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@emain/emain-window.ts` around lines 546 - 550, removeTabViewLater currently
ignores its _delayMs parameter and always uses a hardcoded 1000ms; either use
the passed value or drop the unused parameter. To fix, update removeTabViewLater
to pass the delay to setTimeout (replace the literal 1000 with the delay
parameter and consider renaming _delayMs to delayMs) so the call to
this.removeTabView(tabId, false) runs after the requested delay, or remove the
_delayMs parameter from the method signature and keep the 1000ms literal if that
fixed delay is intentional.

821-821: Consider removing the unused variable entirely.

_workspaceHasWindow is computed but never used. The underscore prefix acknowledges this, but the variable could be removed entirely to simplify the code.

♻️ Remove unused variable
         const workspaceList = await WorkspaceService.ListWorkspaces();
 
-        const _workspaceHasWindow = !!workspaceList.find((wse) => wse.workspaceid === workspaceId)?.windowid;
-
         const choice = dialog.showMessageBoxSync(this, {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@emain/emain-window.ts` at line 821, Remove the unused variable declaration
_workspaceHasWindow — the computed value const _workspaceHasWindow =
!!workspaceList.find((wse) => wse.workspaceid === workspaceId)?.windowid is
never referenced, so delete that line (and any leftover underscore-prefixed
placeholder) from emain-window.ts to simplify the code and avoid dead variables.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@emain/emain-window.ts`:
- Around line 483-510: Delete the dead private method
repositionTabsSlowly(delayMs: number) from the class: remove its entire
implementation (the code referencing activeTabView, getContentBounds(),
delay(...) and calling finalizePositioning()) since it is no longer referenced
anywhere; ensure there are no remaining references to repositionTabsSlowly and
run a quick compile to confirm no usages remain.

---

Nitpick comments:
In `@emain/emain-window.ts`:
- Around line 465-466: The await on tabView.webContents.send("wave-init", ...)
is misleading because webContents.send returns void; either remove the await to
make the fire-and-forget behavior explicit, or (if you intended to wait for the
renderer to be ready like for new tabs) replace the await on send with awaiting
the renderer-ready promise used elsewhere (e.g., await tabView.waveReadyPromise
or whatever waveReadyPromise is named) before calling
this.finalizePositioning(); locate the send call and finalizePositioning() in
emain-window.ts and apply one of these two fixes so the code’s behavior and
intent are consistent.
- Around line 546-550: removeTabViewLater currently ignores its _delayMs
parameter and always uses a hardcoded 1000ms; either use the passed value or
drop the unused parameter. To fix, update removeTabViewLater to pass the delay
to setTimeout (replace the literal 1000 with the delay parameter and consider
renaming _delayMs to delayMs) so the call to this.removeTabView(tabId, false)
runs after the requested delay, or remove the _delayMs parameter from the method
signature and keep the 1000ms literal if that fixed delay is intentional.
- Line 821: Remove the unused variable declaration _workspaceHasWindow — the
computed value const _workspaceHasWindow = !!workspaceList.find((wse) =>
wse.workspaceid === workspaceId)?.windowid is never referenced, so delete that
line (and any leftover underscore-prefixed placeholder) from emain-window.ts to
simplify the code and avoid dead variables.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 1091022b-ecd7-4b80-8f64-4913276d9c06

📥 Commits

Reviewing files that changed from the base of the PR and between 4ee003a and f250fb9.

📒 Files selected for processing (2)
  • electron.vite.config.ts
  • emain/emain-window.ts

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@emain/emain-window.ts`:
- Around line 396-398: The new WaveTabView (tabView) is being added at full
window bounds before initialization finishes, making it topmost and obscuring
the current tab; set the tab off-screen before adding it and only let it move
on-screen after initialization completes. Specifically, call
tabView.setBounds(...) with off-screen coordinates (e.g., x: -9999, y: 0, width:
winBounds.width, height: winBounds.height or similar off-screen x) before
this.contentView.addChildView(tabView), then wait for waveReadyPromise and call
tabView.finalizePositioning() (or the existing positioning helper) to set the
real on-screen bounds and z-order once init succeeds; use getContentBounds(),
tabView.setBounds, this.contentView.addChildView, waveReadyPromise, and
tabView.finalizePositioning() to implement this change.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 4bc1bc7e-7d2f-49dd-b121-d4ae4b9b91a3

📥 Commits

Reviewing files that changed from the base of the PR and between f250fb9 and 408f52b.

📒 Files selected for processing (1)
  • emain/emain-window.ts

Comment on lines +396 to 398
const winBounds = this.getContentBounds();
tabView.setBounds({ x: 0, y: 0, width: winBounds.width, height: winBounds.height });
this.contentView.addChildView(tabView);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Check file size and structure
wc -l emain/emain-window.ts

Repository: wavetermdev/waveterm

Length of output: 89


🏁 Script executed:

# Read the relevant section around lines 396-398 with context
sed -n '380,420p' emain/emain-window.ts

Repository: wavetermdev/waveterm

Length of output: 1801


🏁 Script executed:

# Look for the initializeTab method definition
rg -n "initializeTab" emain/emain-window.ts -B 2 -A 15

Repository: wavetermdev/waveterm

Length of output: 1805


🏁 Script executed:

# Search for waveReadyPromise usage
rg -n "waveReadyPromise" emain/emain-window.ts -B 2 -A 2

Repository: wavetermdev/waveterm

Length of output: 338


🏁 Script executed:

# Look for finalizePositioning method
rg -n "finalizePositioning" emain/emain-window.ts -B 2 -A 10

Repository: wavetermdev/waveterm

Length of output: 1898


🏁 Script executed:

# Find the WaveTabView class and understand positionTabOnScreen/positionTabOffScreen
rg -n "positionTabOnScreen|positionTabOffScreen" emain/emain-window.ts -B 2 -A 5

Repository: wavetermdev/waveterm

Length of output: 1568


🏁 Script executed:

# Check what happens before and after initializeTab is called - look at setTabViewIntoWindow
rg -n "setTabViewIntoWindow" emain/emain-window.ts -B 5 -A 30

Repository: wavetermdev/waveterm

Length of output: 3304


🏁 Script executed:

# Search for where activeTabView is set to understand which tab becomes active
rg -n "activeTabView\s*=" emain/emain-window.ts -B 2 -A 2

Repository: wavetermdev/waveterm

Length of output: 971


🏁 Script executed:

# Look for any other view positioning or visibility logic around initialization
rg -n "addChildView|removeChildView|setVisible" emain/emain-window.ts -B 1 -A 1

Repository: wavetermdev/waveterm

Length of output: 506


🏁 Script executed:

# Search for the WaveTabView class definition and positionTabOnScreen implementation
rg -n "class WaveTabView" emain/ -A 30

Repository: wavetermdev/waveterm

Length of output: 2124


🏁 Script executed:

# Look for positionTabOnScreen and positionTabOffScreen in the codebase
rg -n "positionTabOnScreen\s*(" emain/ -A 10

Repository: wavetermdev/waveterm

Length of output: 160


🏁 Script executed:

# Check if there's any initialization of the WaveTabView that might affect visibility
rg -n "new WaveTabView" emain/ -B 5 -A 10

Repository: wavetermdev/waveterm

Length of output: 1277


🏁 Script executed:

# Check what getOrCreateWebViewForTab does and if it initializes the view
rg -n "getOrCreateWebViewForTab" emain/ -A 20

Repository: wavetermdev/waveterm

Length of output: 4365


🏁 Script executed:

# Search for positionTabOnScreen and positionTabOffScreen definitions
rg -n "positionTabOnScreen|positionTabOffScreen" emain/emain-tabview.ts -A 15

Repository: wavetermdev/waveterm

Length of output: 981


🏁 Script executed:

# Check if there's any visibility or z-order related code in WaveTabView
rg -n "setVisible|zIndex|isVisible" emain/emain-tabview.ts -B 2 -A 5

Repository: wavetermdev/waveterm

Length of output: 46


🏁 Script executed:

# Look for any initialization that might set the view's initial position
rg -n "setBounds" emain/emain-tabview.ts -B 3 -A 3

Repository: wavetermdev/waveterm

Length of output: 402


🏁 Script executed:

# Check if there's any logic that positions the old tab off-screen BEFORE adding the new tab
rg -n "positionTabOffScreen|removeChildView" emain/emain-window.ts -B 3 -A 2

Repository: wavetermdev/waveterm

Length of output: 755


🏁 Script executed:

# Look for any code that might position tabs off-screen when they lose focus or become inactive
rg -n "isActiveTab\s*=" emain/emain-window.ts -B 5 -A 5

Repository: wavetermdev/waveterm

Length of output: 715


🏁 Script executed:

# Check the full context of where finalizePositioning is called relative to setTabViewIntoWindow
sed -n '448,475p' emain/emain-window.ts

Repository: wavetermdev/waveterm

Length of output: 1349


🏁 Script executed:

# Final verification: Check if there are any other similar patterns or related code
rg -n "addChildView" emain/emain-window.ts -B 5 -A 2

Repository: wavetermdev/waveterm

Length of output: 591


🏁 Script executed:

# Verify if positionTabOffScreen is the intended pattern
rg -n "positionTabOffScreen\(" emain/emain-window.ts

Repository: wavetermdev/waveterm

Length of output: 121


Attach the new WaveTabView off-screen until initialization completes.

Lines 397–398 position the tab at full window bounds before waveReadyPromise resolves. Since addChildView appends to the child list and Electron uses child order for z-layer ordering, this uninitialized tab becomes topmost and obscures the current tab during slow initialization or workspace switches. Move it off-screen here and let finalizePositioning() bring it on-screen only after init succeeds.

💡 Suggested change
         const winBounds = this.getContentBounds();
-        tabView.setBounds({ x: 0, y: 0, width: winBounds.width, height: winBounds.height });
+        tabView.setBounds({ x: -15000, y: -15000, width: winBounds.width, height: winBounds.height });
         this.contentView.addChildView(tabView);
📝 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
const winBounds = this.getContentBounds();
tabView.setBounds({ x: 0, y: 0, width: winBounds.width, height: winBounds.height });
this.contentView.addChildView(tabView);
const winBounds = this.getContentBounds();
tabView.setBounds({ x: -15000, y: -15000, width: winBounds.width, height: winBounds.height });
this.contentView.addChildView(tabView);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@emain/emain-window.ts` around lines 396 - 398, The new WaveTabView (tabView)
is being added at full window bounds before initialization finishes, making it
topmost and obscuring the current tab; set the tab off-screen before adding it
and only let it move on-screen after initialization completes. Specifically,
call tabView.setBounds(...) with off-screen coordinates (e.g., x: -9999, y: 0,
width: winBounds.width, height: winBounds.height or similar off-screen x) before
this.contentView.addChildView(tabView), then wait for waveReadyPromise and call
tabView.finalizePositioning() (or the existing positioning helper) to set the
real on-screen bounds and z-order once init succeeds; use getContentBounds(),
tabView.setBounds, this.contentView.addChildView, waveReadyPromise, and
tabView.finalizePositioning() to implement this change.

@sawka sawka merged commit 884884f into main Mar 20, 2026
6 of 7 checks passed
@sawka sawka deleted the sawka/simpler-tabs branch March 20, 2026 20:08
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.

1 participant