Skip to content

Commit 218c875

Browse files
authored
Merge pull request #499 from mohakmalviya/codex/fix-overlay-window-sizing
Fix overlay resizing and narrow layout
2 parents 2ba1937 + 95088a9 commit 218c875

10 files changed

Lines changed: 1302 additions & 80 deletions

File tree

electron/WindowHelper.ts

Lines changed: 42 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -416,8 +416,21 @@ export class WindowHelper {
416416
// EDGE CASE: if the grown window would overflow the work area's right edge,
417417
// the X clamp below shifts the window left — that one case can show a
418418
// one-frame shift, same as any clamped move always could.
419-
public setOverlayDimensionsAnchored(width: number, height: number): void {
420-
if (!this.overlayWindow || this.overlayWindow.isDestroyed()) return;
419+
//
420+
// RETURNS the size actually APPLIED after clamping. The renderer needs this:
421+
// it mirrors the same floor(workArea * 0.9) clamp locally, but the display it
422+
// measures (window.screen.availWidth) and the one this method measures
423+
// (the work area of the display the window sits on) can disagree — on a
424+
// multi-monitor setup they routinely do. Adopting the echoed value keeps the
425+
// renderer's panel width, the toggle anchor and the hover-gate margin locked
426+
// to the window that actually exists, instead of the one it asked for.
427+
public setOverlayDimensionsAnchored(
428+
width: number,
429+
height: number,
430+
): { width: number; height: number } {
431+
if (!this.overlayWindow || this.overlayWindow.isDestroyed()) {
432+
return { width, height };
433+
}
421434

422435
const currentBounds = this.overlayWindow.getBounds();
423436
const currentContentSize = this.overlayWindow.getContentSize();
@@ -456,27 +469,33 @@ export class WindowHelper {
456469
currentContentSize,
457470
computed: { x: newX, y: newY, width: newWidth, height: newHeight },
458471
});
459-
return;
472+
return { width: currentContentSize[0], height: currentContentSize[1] };
460473
}
461474

462475
// Atomic frame change: a single setBounds avoids the 1-frame split where
463476
// the OS window has the new size but the old origin (or vice versa), which
464477
// is what causes the shell to visibly slide and snap during code-expansion.
465478
this.overlayWindow.setBounds({ x: newX, y: newY, width: newWidth, height: newHeight });
466479
this.overlayBounds = this.overlayWindow.getBounds();
480+
const appliedContentSize = this.overlayWindow.getContentSize();
467481
traceOverlayResize('setOverlayDimensionsAnchored:applied', {
468482
requested: { width, height },
469483
appliedBounds: this.overlayBounds,
470-
contentSizeAfter: this.overlayWindow.getContentSize(),
484+
contentSizeAfter: appliedContentSize,
471485
});
486+
return { width: appliedContentSize[0], height: appliedContentSize[1] };
472487
}
473488

474-
// NOTE: the overlay window is a FIXED WIDTH (OVERLAY_DEFAULT_WIDTH = 732)
475-
// for its entire visible lifetime; the renderer always reports that fixed
476-
// width, so every report here is height-only (width delta 0) — top-anchored,
477-
// X never moves, no width setBounds ever. The expand/contract animation is
478-
// CSS-only in the renderer (panel tweens 600↔732 centered inside the fixed
479-
// window). See NativelyInterface.startTransition for the renderer side.
489+
// NOTE: the overlay window's width is FIXED FOR THE WHOLE LIFETIME OF AN
490+
// ANIMATION. It is born at OVERLAY_DEFAULT_WIDTH (732) and only ever changes
491+
// when the USER drags a resize handle (or on restore of a previously dragged
492+
// size) — never during the expand/contract spring, which stays CSS-only in
493+
// the renderer (the panel tweens collapsed↔expanded centered inside the
494+
// window). So every report arriving here DURING an animation is still
495+
// height-only (width delta 0): top-anchored, X never moves, no width
496+
// setBounds. See NativelyInterface.startTransition for the renderer side and
497+
// src/lib/overlayCustomSize.mjs for why the invariant is per-animation
498+
// rather than per-lifetime.
480499

481500
public createWindow(): void {
482501
if (this.launcherWindow !== null) return; // Already created
@@ -1392,12 +1411,22 @@ export class WindowHelper {
13921411
this.repositionOverlayPopovers();
13931412
}
13941413

1395-
// The panel's live LEFT margin inside the fixed window: (732 - panelW)/2,
1396-
// derived from the streamed togglePanelRight = (732 + panelW)/2. Popover
1414+
// The panel's live LEFT margin inside the window: (windowW - panelW)/2,
1415+
// derived from the streamed togglePanelRight = (windowW + panelW)/2. Popover
13971416
// anchors are stored relative to the panel, not the window, so they follow
13981417
// the symmetric width spring.
1418+
//
1419+
// Reads the window's LIVE width rather than OVERLAY_DEFAULT_WIDTH: the user
1420+
// can now resize the overlay, and the renderer streams togglePanelRight
1421+
// against whatever width the window actually has. Using the constant here
1422+
// while the renderer used the live width would offset every popover by
1423+
// (732 - actualWidth) / 2.
13991424
public getOverlayPanelLeftMargin(): number {
1400-
return Math.max(0, WindowHelper.OVERLAY_DEFAULT_WIDTH - this.togglePanelRight);
1425+
const windowWidth =
1426+
this.overlayWindow && !this.overlayWindow.isDestroyed()
1427+
? this.overlayWindow.getContentSize()[0]
1428+
: WindowHelper.OVERLAY_DEFAULT_WIDTH;
1429+
return Math.max(0, windowWidth - this.togglePanelRight);
14011430
}
14021431

14031432
// Re-anchor any open overlay popovers (settings / model-selector) to the

electron/ipcHandlers.ts

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -656,24 +656,29 @@ export function initializeIpcHandlers(appState: AppState): void {
656656
},
657657
);
658658

659-
// X-anchored variant: the window's X origin never moves. The overlay window
660-
// is a FIXED WIDTH (WindowHelper.OVERLAY_DEFAULT_WIDTH = 732) and the
661-
// renderer always reports that width, so in practice this is a pure
662-
// height-only, top-anchored resize. Channel name is historical (it used to
663-
// keep the center fixed across width changes).
659+
// X-anchored variant: the window's X origin never moves. The renderer reports
660+
// the WINDOW width (which only changes when the user drags a resize handle),
661+
// so during an expand/collapse animation this is a pure height-only,
662+
// top-anchored resize. Channel name is historical (it used to keep the center
663+
// fixed across width changes).
664+
//
665+
// RESOLVES with the size actually applied after the main-process clamp, so
666+
// the renderer can adopt it instead of drifting from the real window. See
667+
// WindowHelper.setOverlayDimensionsAnchored.
664668
safeHandle(
665669
'update-content-dimensions-centered',
666670
async (event, { width, height }: { width: number; height: number }) => {
667-
if (!width || !height) return;
671+
if (!width || !height) return undefined;
668672
const senderWebContents = event.sender;
669673
const overlayWin = appState.getWindowHelper().getOverlayWindow();
670674
if (
671675
overlayWin &&
672676
!overlayWin.isDestroyed() &&
673677
overlayWin.webContents.id === senderWebContents.id
674678
) {
675-
appState.getWindowHelper().setOverlayDimensionsAnchored(width, height);
679+
return appState.getWindowHelper().setOverlayDimensionsAnchored(width, height);
676680
}
681+
return undefined;
677682
},
678683
);
679684

@@ -777,11 +782,11 @@ export function initializeIpcHandlers(appState: AppState): void {
777782
appState.getWindowHelper().isOverlayGroupDragManaged(),
778783
);
779784

780-
// (Removed) 'animate-overlay-width' — the overlay window is a FIXED WIDTH
781-
// (WindowHelper.OVERLAY_DEFAULT_WIDTH = 732) and is NEVER width-resized.
782-
// The expand/contract animation is CSS-only in the renderer (the panel
783-
// tweens 600↔732 centered inside the fixed window), so every
784-
// 'update-content-dimensions-centered' report is height-only — a
785+
// (Removed) 'animate-overlay-width' — the overlay window's width changes ONLY
786+
// on an explicit user resize, never as part of the expand/contract animation.
787+
// That animation is CSS-only in the renderer (the panel tweens
788+
// collapsed↔expanded centered inside the window), so every
789+
// 'update-content-dimensions-centered' report during it is height-only — a
785790
// top-anchored resize that does not move X. No sideways jump, no per-frame
786791
// transparent-window re-raster. See NativelyInterface.startTransition.
787792

electron/preload.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,10 @@ type DirectAssistEvent =
4848
// Types for the exposed Electron API
4949
interface ElectronAPI {
5050
updateContentDimensions: (dimensions: { width: number; height: number }) => Promise<void>;
51-
updateContentDimensionsCentered: (dimensions: { width: number; height: number }) => Promise<void>;
51+
updateContentDimensionsCentered: (dimensions: {
52+
width: number;
53+
height: number;
54+
}) => Promise<{ width: number; height: number } | undefined>;
5255
sendOverlayUiState: (state: Record<string, unknown>) => Promise<void>;
5356
onOverlayUiState: (callback: (state: Record<string, unknown>) => void) => () => void;
5457
sendOverlayToggleAnchor: (payload: { panelRight: number }) => Promise<void>;

0 commit comments

Comments
 (0)