Follows the core change: a pane is now the plugin's REAL panel element,
moved into the pop-out window and still running the plugin's own code
(got-feedback/feedback#928).
That forces one thing here, and it is worth being loud about it:
WE MUST NOT CREATE THE PANE WINDOW.
To move a live DOM node into another window, the renderer needs a handle on
that window's document. A BrowserWindow we construct in the main process
gives it no such handle. So the renderer opens the window itself with
window.open(), Electron's setWindowOpenHandler turns that into a real
BrowserWindow anyway, and we recognise it in did-create-window by the frame
name the renderer gave it (`fbpane-<paneId>`) and attach the OS behaviour:
remembered bounds, off the taskbar, minimize-to-tray, listed in the tray.
Create the window here instead and the whole feature collapses back into
"reimplement the panel in the pop-out and sync it over IPC" — which is
exactly what we just deleted.
The IPC surface shrinks to two channels, because main never creates or
destroys a pane window and never looks inside one:
pane:sync renderer → main the registry, so the tray can list panes
pane:toggle main → renderer the tray asking for a pane; only the
renderer knows what opening one means (it
might belong in the dock, and its element
lives there)
Gone: pane:open, pane:close, pane:focus, pane:setAlwaysOnTop, pane:closed.
The renderer holds the WindowProxy for a window it opened, so it already
knows when the user closes it — and it has to, because its element is inside
and must be brought home.
Signed-off-by: topkoa <topkoa@gmail.com>
feedBack core gained a pane system (window.feedBack.panes): live UI — a
mixer, a camera rig, a readout — authored once and hostable anywhere. In
a browser it pops out via window.open(). This gives it the desktop
treatment: a real BrowserWindow that remembers where you put it, can
float above everything, and lives in the system tray.
First Tray in the app. It exists because a popped-out pane is furniture:
you want it out of the way while you play and back instantly when you
don't — not hunted for behind the main window, and not cluttering the
taskbar. Minimizing a pane sends it to the tray; the tray menu lists
every pane with a checkmark and toggles it.
## The renderer owns the truth
Main never looks inside a pane. It owns OS surfaces only — windows and
their geometry — and learns what panes exist from a `pane:sync` push. The
tray menu is a VIEW of the renderer's registry, not a second copy of it.
When the tray toggles a pane it has no window for, it asks the renderer,
because only the renderer knows what opening one means (it might belong
in the dock).
## The pane window loads OUR origin, and that is load-bearing
A pane is fed over BroadcastChannel, which only reaches windows in the
same Chromium instance and origin. Push the URL anywhere else and the
pane opens looking perfect and never updates again. So `pane:open`
validates the URL against the same origin predicate the navigation guards
use (makeRendererOriginPredicate) and refuses anything else outright —
which also means we can never open arbitrary web content with the full
preload bridge attached. It is the same reason main.ts's
setWindowOpenHandler answers same-origin URLs with `allow` rather than
`deny` + openExternal.
## Details that bite
- sanitizeWindowBounds hard-floored at the MAIN window's 800x600. A 380x560
pane restored through it would be silently inflated threefold. It now takes
a WindowSizing; the main window passes its old values as the default, so
every existing call site and the existing test are byte-for-byte unchanged.
- Pane geometry lives in the DESKTOP config, not the renderer's localStorage
— localStorage is shared with the pane windows themselves (same origin), so
a second writer there would race. setDesktopConfig merges shallowly, so
paneWindows is read-modify-written or one pane's save would drop the rest.
- Pane windows are destroyed when the main window closes. Without the
renderer there is nothing on the other end of their channel, so they would
sit showing a frozen playhead forever — and a pane HIDDEN in the tray is
still an open window, which would stop `window-all-closed` from ever firing
and leave the app running as an invisible process.
- Geometry is persisted on move/resize, not only on close: a pane window can
outlive the app in a crash, and the entire point is that you never place it
twice.
- Electron's 'minimize' is not cancellable here (the listener takes no event),
so a pane hides right after minimizing rather than preventing it. The window
is skipTaskbar, so there is no animation to see.
- The tray icon is copied to dist/main/ by build:ts, the same trick
splash.html and spinner.json use — so __dirname resolves it identically in
dev and inside a packaged asar, with no app.isPackaged branch and nothing
added to electron-builder's extraResources. An unreadable icon logs and
skips the tray rather than creating an invisible one whose menu no one can
ever reach.
Needs the matching core change (got-feedback/feedback#928), which registers
the `desktop` host when this bridge is present and falls back to a browser
pop-up when it isn't. An older core simply never calls these channels.
Signed-off-by: topkoa <topkoa@gmail.com>
The startup splash window and its initial status message still showed
the old "Slopsmith" name. Update the brand label, the static status
line, and the JS fallback in splash.html, plus the main-process startup
status snapshot that overrides it, to the new "fee[dB]ack" branding.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: topkoa <topkoa@gmail.com>