Skip to content

Commit 66d31c3

Browse files
authored
Fix env-select popup placement on narrow windows and dismissal on blur (#1037)
* Keep the env-select popup on screen and dismiss it on window blur On a narrow window the 600px popup was positioned by subtracting its width and padding from the title bar width, which goes negative once the window approaches its 400px minimum. The popup then rendered clipped off the left edge with its search box unreachable, so clamp the left coordinate to zero. The popup also lingered when the window lost OS focus, since the blur handler only deactivated the title bar. Because the popup is a child view of the same window, focusing it does not blur the window, so a blur only fires on a real focus loss such as Alt-Tab, which is exactly when the popup should close. Hide it from that handler. I built and verified this with Claude Code, checking the clamp against a unit test that reproduces the off-screen coordinate on a 400px window. * fix: cap the env popup width to the window so nothing is clipped Clamping only the x moved the clipping from the left edge to the right, so on a narrow window the search box became reachable but the config button and the environment names were cut off. The popup is a fixed 600px while the window minWidth is 400, so it can never fit at that size by position alone. Cap the popup width to the space between the left edge and its right anchor instead. The popup content is responsive, so it reflows to fit and the whole thing stays on screen. Verified on a built app at a 470px window: the popup renders fully inside the window (x 0, width 437), with the search box, the config button and the full environment list all visible. Caught the earlier clamp-only version by looking at a before/after screenshot with Claude Code.
1 parent e050646 commit 66d31c3

2 files changed

Lines changed: 38 additions & 3 deletions

File tree

src/main/sessionwindow/sessionwindow.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,7 @@ export class SessionWindow implements IDisposable {
182182
});
183183
this._window.on('blur', () => {
184184
titleBarView.deactivate();
185+
this._hideEnvSelectPopup();
185186
});
186187

187188
titleBarView.load();
@@ -1356,18 +1357,23 @@ export class SessionWindow implements IDisposable {
13561357
}
13571358

13581359
const titleBarRect = this._titleBarView.view.getBounds();
1359-
const popupWidth = 600;
13601360
const paddingRight = process.platform === 'darwin' ? 33 : 127;
1361+
// Anchor the popup's right edge near the env button and cap its width to
1362+
// what fits, so on a narrow window it shrinks (the popup content is
1363+
// responsive) instead of hanging off an edge with the search box or the
1364+
// env list clipped.
1365+
const rightEdge = titleBarRect.width - paddingRight;
1366+
const popupWidth = Math.min(600, rightEdge);
13611367
// shorten browser view height if larger than max allowed
13621368
const maxHeight = Math.min(
13631369
this._envSelectPopup.getScrollHeight(),
13641370
defaultEnvSelectPopupHeight
13651371
);
13661372

13671373
this._envSelectPopup.view.view.setBounds({
1368-
x: Math.round(titleBarRect.width - paddingRight - popupWidth),
1374+
x: Math.round(rightEdge - popupWidth),
13691375
y: Math.round(titleBarRect.height),
1370-
width: popupWidth,
1376+
width: Math.round(popupWidth),
13711377
height: Math.round(maxHeight)
13721378
});
13731379
}

test/unit/sessionwindow-dispose.test.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,32 @@ describe('SessionWindow._disposeSession', () => {
2323
await expect(win._disposeSession()).resolves.toBeUndefined();
2424
});
2525
});
26+
27+
describe('SessionWindow._resizeEnvSelectPopup', () => {
28+
it('keeps the popup on screen when the window is narrower than the popup', () => {
29+
// Arrange: a 400px title bar (the window minWidth) is narrower than the
30+
// 600px popup, so an unclamped x would place the popup off the left edge.
31+
const setBounds = vi.fn();
32+
const win = makeWindow({
33+
_envSelectPopupVisible: true,
34+
_titleBarView: {
35+
view: { getBounds: () => ({ width: 400, height: 60 }) }
36+
},
37+
_envSelectPopup: {
38+
getScrollHeight: () => 300,
39+
view: { view: { setBounds } }
40+
}
41+
});
42+
43+
// Act
44+
win._resizeEnvSelectPopup();
45+
46+
// Assert: the whole popup fits inside the window, so neither the search box
47+
// on the left nor the env list on the right is clipped. Clamping x alone
48+
// would only move the clipping to the other edge, so the width is capped too.
49+
expect(setBounds).toHaveBeenCalledTimes(1);
50+
const bounds = setBounds.mock.calls[0][0];
51+
expect(bounds.x).toBeGreaterThanOrEqual(0);
52+
expect(bounds.x + bounds.width).toBeLessThanOrEqual(400);
53+
});
54+
});

0 commit comments

Comments
 (0)