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:
2026-09-23 16:17:38 +02:00
co-authored by Claude
parent 0a0c708140
commit 2bac7678ca
64 changed files with 144 additions and 7194 deletions
+3 -3
View File
@@ -16,8 +16,8 @@ export interface MenuItem {
tooltip?: string;
/** Shortcut hint, rendered right-aligned. */
accelerator?: string;
/** Greyed and unclickable. Win7 menus disable rather than hide, so the set
* of commands stays in the same place from one right-click to the next. */
/** Greyed and unclickable. Menus disable rather than hide, so the set of
* commands stays in the same place from one right-click to the next. */
disabled?: boolean;
}
@@ -133,7 +133,7 @@ export class ContextMenuService {
* assume `activeWorkspace`.
*
* Everything illegal is DISABLED, not omitted: a menu whose items move
* between right-clicks is a Win7 sin, and "Close Tab" greyed out explains
* between right-clicks is worse than a dead row, and "Close Tab" greyed out explains
* why the last tab cannot be closed better than its absence does.
*
* Duplicate/Rename/Save-session are deliberately absent until tabs have an
+4 -24
View File
@@ -8,9 +8,6 @@ declare global {
__APP_PLATFORM?: string;
/** Which root component this window boots. */
__APP_MODE?: 'main' | 'editor' | 'manager';
/** Which skin tree this window boots. Baked into the preload because
* settings resolve too late to decide layout without a flash. */
__APP_SKIN?: 'modern' | 'win7';
/** Window-kind specific context (EditorOpenContext for editor windows). */
__APP_CTX?: unknown;
}
@@ -24,8 +21,7 @@ export const IS_WINDOWS = PLATFORM === 'win32';
export const IS_LINUX = PLATFORM === 'linux';
/** Shortcut-hint labels: "⌘W" on macOS, "Ctrl+W" elsewhere. Keyed on the real
* platform — the keyboard does not change with the window chrome. */
/** Shortcut-hint labels: "⌘W" on macOS, "Ctrl+W" elsewhere. */
export const MOD_LABEL = IS_MAC ? '⌘' : 'Ctrl+';
export const ALT_LABEL = IS_MAC ? '⌥' : 'Alt+';
@@ -35,25 +31,9 @@ export const APP_MODE: 'main' | 'editor' | 'manager' =
export const EDITOR_CONTEXT =
typeof window !== 'undefined' ? (window.__APP_CTX as EditorOpenContext | undefined) : undefined;
/**
* Which skin tree this window booted. Read from the preload rather than from
* SettingsService because both are needed before first paint — main.ts picks a
* root component from it, and it must not change under a running window (the
* native frame is fixed at BrowserWindow construction).
*/
export const APP_SKIN: 'modern' | 'win7' =
typeof window !== 'undefined' ? (window.__APP_SKIN ?? 'modern') : 'modern';
export const IS_WIN7_CHROME = APP_SKIN === 'win7';
/**
* True when macOS traffic lights overlay the app's own header row — the fused
* `hiddenInset` layout, which reserves `pl-24` for them. False under the win7
* skin even on macOS: `titleBarStyle: 'hidden'` removes the traffic lights
* altogether, so reserving space would leave a gap. Only the shadlike tree
* reads this; 7like draws its own title bar and never defers to native chrome.
*/
export const IS_MAC_CHROME = IS_MAC && !IS_WIN7_CHROME;
/** True when macOS traffic lights overlay the app's own header row (the fused
* hiddenInset layout, see chromeOptions), which reserves `pl-24` for them. */
export const IS_MAC_CHROME = IS_MAC;
/**
* Kill the webview's default right-click menu (Reload, Inspect Element…)
-10
View File
@@ -32,7 +32,6 @@ export class RpcService {
private managerStateListeners = new Set<(open: boolean) => void>();
private connectionsListeners = new Set<() => void>();
private updateStatusListeners = new Set<(status: UpdateStatus) => void>();
private windowStateListeners = new Set<(maximized: boolean) => void>();
private rpc: ReturnType<typeof Electroview.defineRPC<AppRPC>> | undefined;
private ready: Promise<void> = Promise.resolve();
@@ -72,9 +71,6 @@ export class RpcService {
updateStatus: (status) => {
for (const listener of this.updateStatusListeners) listener(status);
},
windowState: ({ maximized }) => {
for (const listener of this.windowStateListeners) listener(maximized);
},
},
},
});
@@ -136,12 +132,6 @@ export class RpcService {
this.updateStatusListeners.add(listener);
}
/** win7 skin: the frameless window's maximize state changed (derived from
* the native resize event, so Aero Snap and Win+Up land here too). */
onWindowState(listener: (maximized: boolean) => void): void {
this.windowStateListeners.add(listener);
}
/** Typed request proxy: `await rpc.request.listDir({...})`. Waits for the bridge. */
get request() {
const rpc = this.rpc;
+1 -37
View File
@@ -7,7 +7,6 @@ import { RpcService } from './rpc.service';
@Injectable({ providedIn: 'root' })
export class SettingsService {
readonly locale = signal<AppSettings['locale']>(DEFAULT_SETTINGS.locale);
readonly skin = signal<AppSettings['skin']>(DEFAULT_SETTINGS.skin);
readonly theme = signal<AppSettings['theme']>(DEFAULT_SETTINGS.theme);
readonly darkTone = signal<AppSettings['darkTone']>(DEFAULT_SETTINGS.darkTone);
readonly accent = signal<AppSettings['accent']>(DEFAULT_SETTINGS.accent);
@@ -51,7 +50,6 @@ export class SettingsService {
private applyAll(settings: AppSettings): void {
this.locale.set(settings.locale);
this.skin.set(settings.skin);
this.theme.set(settings.theme);
this.darkTone.set(settings.darkTone);
this.accent.set(settings.accent);
@@ -64,9 +62,6 @@ export class SettingsService {
this.autoClearCompleted.set(settings.autoClearCompleted);
this.editor.set(settings.editor);
this.editorApp.set(settings.editorApp);
// NO applySkin here, deliberately — see setSkin. applyAll also runs on the
// settingsChanged push, so flipping the class here would break every OPEN
// editor and manager window too, not just the one that changed the setting.
this.applyTheme();
this.applyDarkTone();
this.applyAccent();
@@ -80,30 +75,6 @@ export class SettingsService {
void this.persist();
}
/**
* Saves the preference and NOTHING else — the skin is a launch-time choice.
*
* It deliberately does not touch the `.win7` class, which is the whole bug
* this replaced: each skin's CSS is written against its OWN component tree's
* markup, and the tree is fixed at bootstrap from the preload-baked
* __APP_SKIN. Flipping the class live therefore painted 7.css onto shadlike
* markup that has no .window.glass, no .title-bar, no menu[role=tablist] and
* no <table> ListView — a broken window, not a reskinned one.
*
* A reload would not have helped either: the preload string is frozen at
* webview construction (a persistent WebKitUserScript), and titleBarStyle /
* transparent have no setters, so the native frame cannot change on a live
* window. win7 -> modern without a reframe is the dangerous direction — the
* modern tree draws no title bar, so it would land in a frameless window
* with no way to move, minimize or close it.
*
* The options row shows the saved value as pending until the next start.
*/
setSkin(skin: AppSettings['skin']): void {
this.skin.set(skin);
void this.persist();
}
setTheme(theme: AppSettings['theme']): void {
this.theme.set(theme);
this.applyTheme();
@@ -171,12 +142,6 @@ export class SettingsService {
void this.persist();
}
// There is deliberately no applySkin(). The `.win7` class is written once by
// buildPreload at DOCUMENT_START for the skin this window actually booted,
// and must never change afterwards — see setSkin for why. Every other apply*
// below is safe to run live because it only re-points tokens, and both skins
// consume the same token system.
private applyTheme(): void {
const theme = this.theme();
if (theme === 'system') {
@@ -205,7 +170,7 @@ export class SettingsService {
}
/** The whole interface scale is ONE declaration: every size in the app is
* rem, so moving the root font-size moves both skins in lockstep. 1 = the
* rem, so moving the root font-size moves the whole UI. 1 = the
* browser-default 16px root; delete-when-default keeps the pristine state
* attribute-free like the other axes. buildPreload mirrors this for a
* flash-free first paint. */
@@ -225,7 +190,6 @@ export class SettingsService {
protected settings(): AppSettings {
return {
locale: this.locale(),
skin: this.skin(),
theme: this.theme(),
darkTone: this.darkTone(),
accent: this.accent(),