Remove the Windows 7 (7.css) skin
The win7 skin is dropped. Everything else the branch built stays: Linux support and the HTML context menus, the workspace-tab menu, the continuous interface scale (rem everywhere, stepper, Ctrl/Cmd +/-/0) and the core/ service layer. The modern tree already carried all of them, and nothing outside skins/7like/ imported the win7 tree except main.ts and styles.css. Removed: the skins/7like/ tree (5.1k lines, fonts), the 7.css dependency, the skin setting and its options row + strings in all 7 locales, the frameless-window plumbing (window-control RPCs, windowState push, trackMaximizeState, the synthesized resize grip and its postinstall watchdog), the session-skin pinning in buildPreload/chromeOptions, the skin/skinfix cascade layers, and the win7-only probes and screenshot harness (the harness cannot render the modern tree: plain Firefox has no RPC bridge). chromeOptions() stays as the one shared frame decision. Fixed on the way out: - index.html's per-theme pre-paint background is restored. The branch had replaced it with a win7-only transparent rule, so modern dark windows had silently lost one of the three flash guards. - editor-windows.ts wrote its window-key separator as a literal NUL byte, which made git and grep treat the file as binary; it is `\0` now (same string at runtime). No settings migration: a stale "skin" key is carried harmlessly by the store's spread-merge and dropped on the next save. UI bundle: 616.7 kB -> 405.4 kB, back under the 500 kB budget. Docs: architecture, gotchas and platform-notes lose the win7 material but keep what stays true (construction-time preloads/frames, drag regions ignoring preventDefault, electrobun's missing resize/maximize primitives); flash-prevention, dev-URL and Bun-panic notes move back under Electrobun; stale context-menu/tab-bar links fixed; README documents the scale keys. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
+10
-89
@@ -1,31 +1,9 @@
|
||||
import { BrowserWindow } from "electrobun/bun";
|
||||
import { DEFAULT_SETTINGS, type AppSettings } from "../shared/types";
|
||||
|
||||
/**
|
||||
* The skin THIS PROCESS booted with, frozen at startup by `initSessionSkin`.
|
||||
*
|
||||
* Every window must be built from this, never from the live settings snapshot.
|
||||
* The skin picks a whole component tree (bootstrapped from the preload-baked
|
||||
* __APP_SKIN) and the native frame (constructor-only), so it cannot change
|
||||
* without a restart — but the persisted setting CAN change mid-session. Reading
|
||||
* the live value would boot a newly opened editor or manager into the pending
|
||||
* skin while the main window still runs the old one: two windows, two trees,
|
||||
* two frames.
|
||||
*
|
||||
* Pinning it in one place makes that impossible rather than making each call
|
||||
* site remember. Everything else in the preload keeps using the live snapshot,
|
||||
* because theme/tone/accent/text-size genuinely do apply live.
|
||||
*/
|
||||
let sessionSkin: AppSettings["skin"] = DEFAULT_SETTINGS.skin;
|
||||
|
||||
/** Call once, at startup, before any window is constructed. */
|
||||
export function initSessionSkin(skin: AppSettings["skin"]): void {
|
||||
sessionSkin = skin;
|
||||
}
|
||||
import type { AppSettings } from "../shared/types";
|
||||
|
||||
/**
|
||||
* The preload globals every window gets. Single composition point: the UI
|
||||
* reads them in ui/src/app/platform.ts before first paint (window chrome and
|
||||
* reads them in ui/src/app/core/platform.ts before first paint (window chrome and
|
||||
* root-component selection both depend on them).
|
||||
*
|
||||
* The persisted theme attributes are baked in here — mirroring the
|
||||
@@ -39,22 +17,11 @@ export function buildPreload(
|
||||
settings: AppSettings,
|
||||
ctx?: unknown,
|
||||
): string {
|
||||
// sessionSkin, NOT settings.skin — a pending skin change must not leak into
|
||||
// a window opened before the restart. See initSessionSkin.
|
||||
let script =
|
||||
`window.__APP_PLATFORM=${JSON.stringify(process.platform)};` +
|
||||
`window.__APP_MODE=${JSON.stringify(mode)};` +
|
||||
`window.__APP_SKIN=${JSON.stringify(sessionSkin)};`;
|
||||
`window.__APP_MODE=${JSON.stringify(mode)};`;
|
||||
if (ctx !== undefined) script += `window.__APP_CTX=${JSON.stringify(ctx)};`;
|
||||
|
||||
// A class, not a data-* attribute: 7.scoped.css is precompiled to require a
|
||||
// literal `.win7` ancestor, so the name is not ours to choose. This is the
|
||||
// ONLY place it is ever written — the UI must never toggle it live, because
|
||||
// each skin's CSS is written against its own tree's markup.
|
||||
if (sessionSkin === "win7") {
|
||||
script += `document.documentElement.classList.add("win7");`;
|
||||
}
|
||||
|
||||
const dataset: Record<string, string> = {};
|
||||
if (settings.theme !== "system") dataset["theme"] = settings.theme;
|
||||
if (settings.darkTone !== "dark") dataset["darkTone"] = settings.darkTone;
|
||||
@@ -72,32 +39,14 @@ export function buildPreload(
|
||||
}
|
||||
|
||||
/**
|
||||
* Native chrome options for a window. The ONE place the frame decision is made —
|
||||
* both the main window (src/bun/index.ts) and openSecondaryWindow spread this.
|
||||
*
|
||||
* Both values are BrowserWindow CONSTRUCTOR options — there is no API to
|
||||
* re-decorate a live window — which is half of why the skin is a launch-time
|
||||
* choice. Takes no argument on purpose: it reads the pinned sessionSkin, so no
|
||||
* call site can accidentally frame a window from a pending setting.
|
||||
* Native chrome for every window — the ONE place the frame is decided; the main
|
||||
* window (src/bun/index.ts) and openSecondaryWindow both spread this. macOS
|
||||
* fuses the titlebar with the app's own header row (hiddenInset: the traffic
|
||||
* lights overlay it, which is what IS_MAC_CHROME reserves pl-24 for); other
|
||||
* platforms keep the native titlebar. Constructor-only: there is no API to
|
||||
* re-decorate a live window.
|
||||
*/
|
||||
export function chromeOptions() {
|
||||
const skin = sessionSkin;
|
||||
// win7 draws its own Aero title bar (drag region + our minimize/maximize/
|
||||
// close), so the window is frameless on EVERY platform. `titleBarStyle:
|
||||
// "hidden"` clears `Titled` from the Cocoa style mask, which is also what
|
||||
// removes the macOS traffic lights — there is no separate
|
||||
// hide-traffic-lights API. Linux honours it too: the GTK wrapper calls
|
||||
// gtk_window_set_decorated(false).
|
||||
//
|
||||
// `transparent` is what lets the Aero frame be a real frame: .window's 6px
|
||||
// radius is drawn by the webview OUTSIDE the window's own rectangle, so on
|
||||
// an opaque surface the corners are simply clipped square.
|
||||
// PORTABILITY: on Linux this needs a compositor granting an RGBA visual.
|
||||
if (skin === "win7") return { titleBarStyle: "hidden" as const, transparent: true };
|
||||
|
||||
// modern: the native frame stays. On macOS it fuses with the app's own
|
||||
// header row (hiddenInset), and the traffic lights overlay it — which is
|
||||
// what IS_MAC_CHROME reserves pl-24 for in the shadlike tree.
|
||||
return process.platform === "darwin"
|
||||
? { titleBarStyle: "hiddenInset" as const, trafficLightOffset: { x: 12, y: 13 } }
|
||||
: {};
|
||||
@@ -123,31 +72,6 @@ export function showWhenReady(win: BrowserWindow): void {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Push maximize-state changes to a window's webview so the win7 title bar can
|
||||
* swap its maximize/restore glyph.
|
||||
*
|
||||
* electrobun emits NO maximize/unmaximize event (only close/resize/move/focus/
|
||||
* blur/keyDown/keyUp), so the state is derived from `resize` — which is also
|
||||
* what makes OS-initiated changes work: double-click zoom, Win+Up, Aero Snap
|
||||
* all resize the window. `isMaximized()` is a synchronous FFI read, so polling
|
||||
* it per resize is cheap; the last-value guard keeps the wire quiet during a
|
||||
* drag-resize. The emitter has no off(), so this closure must not capture
|
||||
* anything that needs to be garbage collected (see WindowRegistry.track).
|
||||
*/
|
||||
export function trackMaximizeState(
|
||||
win: BrowserWindow,
|
||||
push: (maximized: boolean) => void,
|
||||
): void {
|
||||
let last = win.isMaximized();
|
||||
win.on("resize", () => {
|
||||
const now = win.isMaximized();
|
||||
if (now === last) return;
|
||||
last = now;
|
||||
push(now);
|
||||
});
|
||||
}
|
||||
|
||||
export interface SecondaryWindowOptions {
|
||||
title: string;
|
||||
/** The once-resolved dev/prod url (see getMainViewUrl in index.ts). */
|
||||
@@ -162,10 +86,7 @@ export interface SecondaryWindowOptions {
|
||||
rpc: NonNullable<ConstructorParameters<typeof BrowserWindow>[0]>["rpc"];
|
||||
}
|
||||
|
||||
/**
|
||||
* App-chromed secondary window. The frame decision is delegated to
|
||||
* chromeOptions (the single frame decision).
|
||||
*/
|
||||
/** App-chromed secondary window; its frame comes from chromeOptions. */
|
||||
export function openSecondaryWindow(opts: SecondaryWindowOptions): BrowserWindow {
|
||||
const win = new BrowserWindow({
|
||||
title: opts.title,
|
||||
|
||||
Reference in New Issue
Block a user