fix(panes): persist alwaysOnTop, flush geometry on quit, test the sizing override

Five more from review.

1. alwaysOnTop was RESTORED but never SAVED. A dead read: the only way to turn
   it on was to hand-edit the config file. There is now one snapshot() that
   decides what "remembered" means, used by the debounced save, the flush on
   close, and the flush on quit — so the three can never disagree about it again.

2. GEOMETRY WAS LOST ON QUIT. closeAllPanes() calls destroy(), and destroy()
   does not fire 'close' — so the flush wired to that event never ran on the one
   path every user takes. Combined with the 400ms debounce: move a pane, quit two
   seconds later, and its position was gone. Every pane is flushed before its
   window is destroyed.

3. The tray-icon comment claimed a 16px PNG; build:ts copies the 32px one. Also
   spelled out what the macOS consequence actually is (a colour icon rather than
   one that adapts to light/dark menu bars), rather than gesturing at it.

4. sanitizeWindowBounds' new `sizing` parameter had no tests — and it is the
   whole reason the function was touched. Without it a 380x560 pane restored
   through the MAIN window's 800x600 floor is silently inflated to three times
   the size the plugin asked for. Four tests now pin it: a small pane is not
   inflated, the min clamp uses the override, corrupt input falls back to the
   override's defaults, and — the one that protects everyone else — omitting
   `sizing` leaves the main window's behaviour byte-for-byte unchanged.

window-bounds tests: 13/13.

Signed-off-by: topkoa <topkoa@gmail.com>
This commit is contained in:
topkoa
2026-07-12 21:12:04 -04:00
parent 3d9e8481ea
commit 313d1e5d5b
3 changed files with 63 additions and 7 deletions
+38
View File
@@ -57,3 +57,41 @@ test('fractional coordinates are rounded to integers', () => {
const out = sanitizeWindowBounds({ x: 10.6, y: 20.4, width: 1200.5, height: 800.2 }, [PRIMARY]);
assert.deepEqual(out, { x: 11, y: 20, width: 1201, height: 800, maximized: false });
});
// ── Custom sizing (pane windows) ────────────────────────────────────────────
//
// sanitizeWindowBounds grew a `sizing` parameter so pane pop-outs could be small.
// Without it, a 380×560 pane restored through the MAIN window's 800×600 floor would
// be silently inflated to 800×600 — three times the size the plugin asked for. The
// default argument keeps every existing call site byte-for-byte identical, so these
// tests exist to pin the override itself, which is the part nothing else covers.
const PANE_SIZING = { minWidth: 240, minHeight: 180, defaultWidth: 380, defaultHeight: 560 };
test('custom sizing: a small pane is NOT inflated to the main window floor', () => {
const out = sanitizeWindowBounds({ x: 100, y: 100, width: 380, height: 560 }, [PRIMARY], PANE_SIZING);
assert.deepEqual(out, { x: 100, y: 100, width: 380, height: 560, maximized: false });
});
test('custom sizing: the min clamp uses the override, not MIN_WIDTH/MIN_HEIGHT', () => {
// Below the pane minimum (240×180) — clamped up to it, and nowhere near 800×600.
const out = sanitizeWindowBounds({ x: 10, y: 10, width: 50, height: 20 }, [PRIMARY], PANE_SIZING);
assert.deepEqual(out, { x: 10, y: 10, width: 240, height: 180, maximized: false });
});
test('custom sizing: missing/corrupt bounds fall back to the override defaults', () => {
assert.deepEqual(
sanitizeWindowBounds(undefined, [PRIMARY], PANE_SIZING),
{ width: 380, height: 560, maximized: false },
);
assert.deepEqual(
sanitizeWindowBounds({ x: 'nope', y: 0, width: 380, height: 560 }, [PRIMARY], PANE_SIZING),
{ width: 380, height: 560, maximized: false },
);
});
test('omitting sizing keeps the main window behaviour exactly as before', () => {
// The whole point of the default argument: existing call sites must not shift.
const out = sanitizeWindowBounds({ x: 10, y: 10, width: 50, height: 20 }, [PRIMARY]);
assert.deepEqual(out, { x: 10, y: 10, width: MIN_WIDTH, height: MIN_HEIGHT, maximized: false });
});