From 0878ae2682e3b0a0218fb7a6b2866d8a2f778291 Mon Sep 17 00:00:00 2001 From: topkoa Date: Sun, 12 Jul 2026 22:32:30 -0400 Subject: [PATCH 1/2] feat(panes): a pane window can never go behind the app MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A pane is a control surface for the thing you are looking at. Clicking the highway to play — the single most common thing anyone does here — raises the main window, and a plain sibling window slides straight behind it. You would fish the mixer back out from behind the game every time you touched the game. That is not a pop-out, it is a hiding place. Parent each pane window to the main window. That is the precise amount of "in front": the pane always floats above fee[dB]ack, and behaves like any other window against everything else. Deliberately NOT setAlwaysOnTop. That would put a pane above the user's browser and editor too — a surprising thing to inflict on someone for opening a mixer. alwaysOnTop still composes on top of this for a pane the user explicitly wants above everything. The trade, accepted: the OS ties parent and child together, so minimizing fee[dB]ack hides its panes and restoring brings them back. That is what a companion window should do. Failure is non-fatal — a pane that can be buried is still a working pane. Signed-off-by: topkoa --- src/main/pane-hosts.ts | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/main/pane-hosts.ts b/src/main/pane-hosts.ts index 1c667be..49596c8 100644 --- a/src/main/pane-hosts.ts +++ b/src/main/pane-hosts.ts @@ -170,6 +170,32 @@ export function adoptPaneWindow(win: BrowserWindow, paneId: string): void { win.setMinimumSize(PANE_SIZING.minWidth, PANE_SIZING.minHeight); if (saved.alwaysOnTop === true) win.setAlwaysOnTop(true); + // A PANE CAN NEVER GO BEHIND THE APP. + // + // A pane is a control surface for the thing you are looking at. Clicking the + // highway to play — the single most common thing anyone does here — raises the + // main window, and a plain sibling window would slide straight behind it. You + // would have to fish the mixer back out from behind the game every time you + // touched the game. That is not a pop-out, it is a hiding place. + // + // Parenting to the main window (rather than setAlwaysOnTop) is the precise + // amount of "in front": the pane always floats above fee[dB]ack, and behaves + // like any other window against everything else. Always-on-top would put it + // above the user's browser and editor too — a surprising thing to inflict on + // someone for opening a mixer. + // + // alwaysOnTop remains available on top of this for a pane the user explicitly + // wants above EVERYTHING; the two compose. + const main = getMainWindow(); + if (main && !main.isDestroyed()) { + try { + win.setParentWindow(main); + } catch (err) { + // Not fatal: a pane that can be buried is still a working pane. + console.warn(`[panes] could not parent ${paneId} to the main window:`, err); + } + } + // A pane is a companion to the app, not an entry to it: keep it off the taskbar // so it never masquerades as a second fee[dB]ack. win.setSkipTaskbar(true); From 47fe1492a6d99b1c22ccced304808c6d64947ad3 Mon Sep 17 00:00:00 2001 From: topkoa Date: Sun, 12 Jul 2026 22:36:53 -0400 Subject: [PATCH 2/2] =?UTF-8?q?fix(panes):=20a=20minimized=20pane=20window?= =?UTF-8?q?=20was=20unrecoverable=20=E2=80=94=20remove=20the=20button?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reported in testing: pop out the stem mixer, minimize it, and it is gone. Not hidden — gone. It appears in no taskbar, no alt-tab list, nothing. The chain: - A pane window is skipTaskbar, so it never masquerades as a second fee[dB]ack. - It is parented to the main window (so it can't go behind the app), which makes it an OWNED window — and Windows will not give an owned window a taskbar button in any case. - On minimize we then hid it "to the tray". So a minimized pane had no taskbar entry and no alt-tab entry, and the only route back was the tray — which Windows tucks behind the overflow chevron by default. "It's in the tray" is not an answer when the user cannot see the tray. The window is no longer minimizable. Every remaining way to put a pane away is one the user can undo from something visible: - close it → the panel returns to the app, where it came from - the tray → per-pane toggle, plus Show/Hide all panes - the chip's stub → in the app, exactly where the panel used to be Plus a restore() on 'minimize' in case anything else (a window manager, a shortcut, a future code path) minimizes it anyway. This is the same class of mistake as hiding the panel behind the pop-out chip: a place to put something, with no way back that the user can find. Signed-off-by: topkoa --- src/main/pane-hosts.ts | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/src/main/pane-hosts.ts b/src/main/pane-hosts.ts index 49596c8..05a5191 100644 --- a/src/main/pane-hosts.ts +++ b/src/main/pane-hosts.ts @@ -209,14 +209,30 @@ export function adoptPaneWindow(win: BrowserWindow, paneId: string): void { win.on('moved', save); win.on('resized', save); - // Minimize sends a pane to the tray, not the taskbar. Panes are small and - // numerous; a taskbar full of them is noise, and the tray already lists them. - // Electron's 'minimize' is not cancellable here (the listener takes no event), - // so we hide right after rather than preventing it — and the window is - // skipTaskbar, so there is no animation to see. + // NO MINIMIZE BUTTON. This is a safety rail, not a style choice. + // + // A pane window is skipTaskbar (it must not masquerade as a second fee[dB]ack) + // and, since it is parented to the main window, an OWNED window — which Windows + // will not give a taskbar button to in any case. So a minimized pane has no + // taskbar entry and no alt-tab entry. The only route back is the tray, and + // Windows hides new tray icons behind the overflow chevron by default. + // + // Net effect if we allow it: the user clicks minimize and the window is gone. + // Not hidden — gone, with no affordance anywhere on screen to bring it back. + // That is exactly what happened in testing, and no amount of "it's in the tray" + // makes it acceptable. + // + // So the button is removed. Every way of putting a pane away that remains is one + // the user can undo from something they can see: + // - close it → the panel returns to the app, where it came from + // - the tray → per-pane toggle, and Show/Hide all panes + // - the chip's stub → in the app, dead centre of where the panel used to be + win.setMinimizable(false); + + // Belt and braces: if something else minimizes it anyway (a window manager, a + // shortcut, a future code path), restore it rather than leave it unreachable. win.on('minimize', () => { - win.hide(); - refreshTray(); + win.restore(); }); // 'close' fires while the window still exists; 'closed' after it is gone. The