Switching Modern -> Win7 at runtime broke the UI. SettingsService.applySkin
toggled the `.win7` class the moment the setting changed — harmless when there
was one component tree, and a broken window as soon as there were two. The tree
is bootstrapped once from the preload-baked __APP_SKIN, so the live flip painted
7.css onto shadlike markup that has no .window.glass, no .title-bar, no
menu[role=tablist] and no <table> ListView. The CSS landed on a tree that cannot
receive it.
A reload would not have helped, and neither would anything short of a restart:
- The preload is a persistent WebKitUserScript injected at DOCUMENT_START, so
its string is frozen at webview construction — loadURL() re-runs it with the
OLD __APP_SKIN. updatePreloadScriptToWebView exists in the FFI but is
reachable only from electrobun's internal <electrobun-webview> handler.
- titleBarStyle/transparent have no setters; a live window cannot be re-framed.
win7 -> modern is the dangerous direction — the modern tree draws no title
bar, so it would land in a frameless transparent window with no way to move,
minimize or close it.
- Recreating the main window is also out: exitOnLastWindowClosed is true, the
main rpc is captured by six push closures, the tray captures `win`, and
listeners cannot be removed.
So: applySkin is deleted outright, from setSkin AND from applyAll — the latter
matters independently, since applyAll runs on the settingsChanged push and was
therefore breaking every open editor and manager window too. Clicking a skin now
only persists it.
Also fixed a second leak found while tracing this: manager-window and
editor-windows build from `currentSettings()`, the LIVE snapshot, so a window
opened after a pending change would boot the new tree and frame while main still
ran the old one. `initSessionSkin` now freezes the skin at startup before any
window exists, and buildPreload/chromeOptions read that pinned value —
chromeOptions loses its parameter so no call site can get it wrong.
The options row shows the saved value as pending (skinPending() compares
settings.skin() against APP_SKIN) with the hint retexted in all 7 locales; it
said "Reopen the window to change its frame", which described the deleted
design.
Verified: tsc + build:ui clean; both skins boot correctly from a persisted
setting (win7 frameless, modern with the native frame). The live flip is now
impossible by construction, which is the check that matters here — the UI bundle
contains no classList write for win7 at all, the only writer is buildPreload,
and sessionSkin has exactly one assignment site.
Not verified: the click itself and the pending hint appearing, which need an
interaction pass — no window automation is available here. Both are present in
the built bundle.
Co-Authored-By: Claude <noreply@anthropic.com>