From fe028a9b01608412628f21e711364010528f8b20 Mon Sep 17 00:00:00 2001 From: Jokin Date: Sun, 16 Aug 2026 01:45:40 +0200 Subject: [PATCH] Stage 1: make this branch Windows 7 only MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The dual-skin arrangement existed to let one build toggle between the modern shadcn look and 7.css. Keeping both correct is what forced the cascade tricks that fight 7.css, so the branch drops the toggle: this build IS the Windows 7 build. - `skin` is gone from AppSettings/DEFAULT_SETTINGS, the settings store, the SettingsService (signal, applySkin, persist payload), the options popover control, and the i18n keys. - buildPreload adds `.win7` unconditionally; `chromeOptions()` no longer takes a skin and always returns frameless `titleBarStyle: "hidden"`. - `IS_WIN7_CHROME` and `IS_MAC_CHROME` are deleted along with every `isMacChrome` branch in the tab bar, editor window and connection manager. Those reserved `pl-24` for macOS traffic lights, which frameless chrome removes on every platform, so the branches were dead by construction. - The title bar is no longer conditional — it is the window's only chrome. - index.html keeps just the win7 anti-flash arm (four theme states). Not done here, deliberately: stripping Tailwind's element styling. The neutralizers in styles.css (notably `.win7 button { min-width: 0 }`) are still load-bearing until each component's markup is converted to 7.css's real contract, so they come out per component rather than in one sweep. Verified: backend `tsc --noEmit` clean, `bun run build:ui` clean; bundle 490.50 kB, down from 492.52 kB. Co-Authored-By: Claude --- src/bun/index.ts | 4 +- src/bun/windows.ts | 44 +++++++------------ src/shared/i18n/en.ts | 6 --- src/shared/types.ts | 8 ---- .../connection-manager.html | 5 --- .../connection-manager/connection-manager.ts | 3 +- ui/src/app/editor-window/editor-window.html | 5 --- ui/src/app/editor-window/editor-window.ts | 3 +- ui/src/app/platform.ts | 23 +--------- ui/src/app/settings.service.ts | 21 --------- ui/src/app/tab-bar/tab-bar.ts | 35 +-------------- ui/src/app/title-bar/title-bar.ts | 12 ++--- ui/src/index.html | 15 ++----- 13 files changed, 31 insertions(+), 153 deletions(-) diff --git a/src/bun/index.ts b/src/bun/index.ts index 37a7a45..a1a3ecb 100644 --- a/src/bun/index.ts +++ b/src/bun/index.ts @@ -86,8 +86,8 @@ const win = new BrowserWindow({ x: 120, y: 120, }, - // Frame decision (per skin) lives in chromeOptions — see windows.ts. - ...chromeOptions(currentSettings.skin), + // Frame decision lives in chromeOptions — see windows.ts. + ...chromeOptions(), // lets the UI pick the fused layout + theme before first paint preload: buildPreload("main", currentSettings), rpc, diff --git a/src/bun/windows.ts b/src/bun/windows.ts index d1cac8f..b28d355 100644 --- a/src/bun/windows.ts +++ b/src/bun/windows.ts @@ -1,7 +1,6 @@ import { BrowserWindow } from "electrobun/bun"; import type { AppSettings } from "../shared/types"; -const isMac = process.platform === "darwin"; /** * The preload globals every window gets. Single composition point: the UI @@ -21,15 +20,13 @@ export function buildPreload( ): string { let script = `window.__APP_PLATFORM=${JSON.stringify(process.platform)};` + - `window.__APP_MODE=${JSON.stringify(mode)};` + - `window.__APP_SKIN=${JSON.stringify(settings.skin)};`; + `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 (mirrors SettingsService.applySkin). - if (settings.skin === "win7") { - script += `document.documentElement.classList.add("win7");`; - } + // literal `.win7` ancestor. Unconditional on this branch — the skin is not a + // setting any more, this build IS the Windows 7 build. + script += `document.documentElement.classList.add("win7");`; const dataset: Record = {}; if (settings.theme !== "system") dataset["theme"] = settings.theme; @@ -43,27 +40,18 @@ export function buildPreload( } /** - * Native chrome options for a window, per skin. The ONE place either skin's - * frame decision is made — both the main window (src/bun/index.ts) and - * openSecondaryWindow spread this. + * 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. * - * - "win7" draws its own Aero title bar in the webview (drag region + our own - * minimize/maximize/close), so the window must be 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. - * - "modern" keeps today's split: macOS fuses the tab bar into an inset - * titlebar (traffic lights overlay the UI's h-10 tab row, offset centers - * them); other platforms keep the native titlebar. - * - * Skin changes therefore only reach the frame on the NEXT window open — the - * CSS half applies live. The settings UI says so. + * The webview draws its own Aero title bar (drag region + our own + * 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). */ -export function chromeOptions(skin: AppSettings["skin"]) { - if (skin === "win7") return { titleBarStyle: "hidden" as const }; - return isMac - ? { titleBarStyle: "hiddenInset" as const, trafficLightOffset: { x: 12, y: 13 } } - : {}; +export function chromeOptions() { + return { titleBarStyle: "hidden" as const }; } /** @@ -127,14 +115,14 @@ export interface SecondaryWindowOptions { /** * App-chromed secondary window. The frame decision is delegated to - * chromeOptions (which owns both the per-skin and the per-platform branch). + * chromeOptions (the single frame decision). */ export function openSecondaryWindow(opts: SecondaryWindowOptions): BrowserWindow { const win = new BrowserWindow({ title: opts.title, url: opts.url, frame: opts.frame, - ...chromeOptions(opts.settings.skin), + ...chromeOptions(), preload: buildPreload(opts.mode, opts.settings, opts.ctx), rpc: opts.rpc, hidden: true, diff --git a/src/shared/i18n/en.ts b/src/shared/i18n/en.ts index cb71fa3..0d32123 100644 --- a/src/shared/i18n/en.ts +++ b/src/shared/i18n/en.ts @@ -37,12 +37,6 @@ export const en = { title: "Options", language: "Language", system: "System", - skin: { - label: "Skin", - modern: "Modern", - win7: "Windows 7", - restartHint: "Reopen the window to change its frame", - }, theme: { label: "Theme", system: "System", light: "Light", dark: "Dark" }, tone: { label: "Tone", dark: "Dark", darker: "Darker" }, accent: { label: "Accent" }, diff --git a/src/shared/types.ts b/src/shared/types.ts index 6261d25..3fbb4a2 100644 --- a/src/shared/types.ts +++ b/src/shared/types.ts @@ -78,13 +78,6 @@ export type AccentTone = export interface AppSettings { /** UI language; "system" resolves to the OS locale (fallback English). */ locale: "system" | SupportedLocale; - /** - * Visual skin. "modern" is the native shadcn-style look; "win7" swaps in - * 7.css (Windows 7 Aero) and switches the window to custom frameless chrome. - * Changing this needs a window reopen to take effect on the frame itself — - * the CSS half applies live. - */ - skin: "modern" | "win7"; theme: "system" | "light" | "dark"; /** Dark appearance: grey ("dark") or near-black AMOLED ("darker"). */ darkTone: "dark" | "darker"; @@ -115,7 +108,6 @@ export interface AppSettings { export const DEFAULT_SETTINGS: AppSettings = { locale: "system", - skin: "modern", theme: "system", darkTone: "dark", accent: "blue", diff --git a/ui/src/app/connection-manager/connection-manager.html b/ui/src/app/connection-manager/connection-manager.html index 7bbbc9f..5da4792 100644 --- a/ui/src/app/connection-manager/connection-manager.html +++ b/ui/src/app/connection-manager/connection-manager.html @@ -9,11 +9,6 @@ elsewhere the native titlebar sits above and this is a compact strip -->

{{ i18n.t('connMgr.title') }}

diff --git a/ui/src/app/connection-manager/connection-manager.ts b/ui/src/app/connection-manager/connection-manager.ts index e3f629c..4c5bd3f 100644 --- a/ui/src/app/connection-manager/connection-manager.ts +++ b/ui/src/app/connection-manager/connection-manager.ts @@ -6,7 +6,7 @@ import { ConnectionsService } from '../connections.service'; import { ConfirmDialog } from '../dialogs/confirm-dialog'; import { PromptDialog } from '../dialogs/prompt-dialog'; import { DialogsService } from '../dialogs.service'; -import { blockNativeContextMenu, IS_MAC_CHROME } from '../platform'; +import { blockNativeContextMenu } from '../platform'; import { RpcService } from '../rpc.service'; import { SettingsService } from '../settings.service'; import { TitleBar } from '../title-bar/title-bar'; @@ -84,7 +84,6 @@ export class ConnectionManager { readonly i18n = inject(I18nService); readonly store = inject(ConnectionsService); readonly dialogs = inject(DialogsService); - readonly isMacChrome = IS_MAC_CHROME; private readonly rpc = inject(RpcService); // injected for its side effects: fetches settings and applies theme/accent/ // text-size data attributes to this window's document root diff --git a/ui/src/app/editor-window/editor-window.html b/ui/src/app/editor-window/editor-window.html index 60ca239..fb3bdfe 100644 --- a/ui/src/app/editor-window/editor-window.html +++ b/ui/src/app/editor-window/editor-window.html @@ -17,11 +17,6 @@ elsewhere the native titlebar sits above and this is a compact strip -->

diff --git a/ui/src/app/editor-window/editor-window.ts b/ui/src/app/editor-window/editor-window.ts index a79720e..a4e4163 100644 --- a/ui/src/app/editor-window/editor-window.ts +++ b/ui/src/app/editor-window/editor-window.ts @@ -11,7 +11,7 @@ import { } from '@angular/core'; import { NgIcon } from '@ng-icons/core'; import { I18nService } from '../i18n.service'; -import { ALT_LABEL, blockNativeContextMenu, EDITOR_CONTEXT, IS_MAC_CHROME, MOD_LABEL } from '../platform'; +import { ALT_LABEL, blockNativeContextMenu, EDITOR_CONTEXT, MOD_LABEL } from '../platform'; import { RpcService } from '../rpc.service'; import { SettingsService } from '../settings.service'; import { ToastService } from '../toast.service'; @@ -42,7 +42,6 @@ const DRAFT_DEBOUNCE_MS = 400; export class EditorWindow { readonly i18n = inject(I18nService); readonly ctx = EDITOR_CONTEXT; - readonly isMacChrome = IS_MAC_CHROME; readonly modLabel = MOD_LABEL; readonly altLabel = ALT_LABEL; diff --git a/ui/src/app/platform.ts b/ui/src/app/platform.ts index bf85cfa..3a094ef 100644 --- a/ui/src/app/platform.ts +++ b/ui/src/app/platform.ts @@ -8,9 +8,6 @@ declare global { __APP_PLATFORM?: string; /** Which root component this window boots. */ __APP_MODE?: 'main' | 'editor' | 'manager'; - /** Persisted skin — decides the window's native chrome, so it has to be - * known before first paint (see chromeOptions in src/bun/windows.ts). */ - __APP_SKIN?: 'modern' | 'win7'; /** Window-kind specific context (EditorOpenContext for editor windows). */ __APP_CTX?: unknown; } @@ -20,30 +17,12 @@ export const PLATFORM = typeof window !== 'undefined' ? (window.__APP_PLATFORM ? export const IS_MAC = PLATFORM === 'darwin'; -/** - * The win7 skin makes the window frameless on EVERY platform and draws its own - * Aero title bar, so this is a window-chrome fact, not just a settings value — - * it is baked into the preload rather than read from SettingsService, which - * resolves too late for layout. Toggling the setting only affects windows - * opened afterwards. - */ -export const IS_WIN7_CHROME = - typeof window !== 'undefined' && window.__APP_SKIN === 'win7'; - -/** - * True when macOS traffic lights overlay the app's 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, so - * reserving space for them would leave a gap. - */ -export const IS_MAC_CHROME = IS_MAC && !IS_WIN7_CHROME; - 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, NOT IS_MAC_CHROME — the keyboard does not change with the skin. */ + * platform — the keyboard does not change with the window chrome. */ export const MOD_LABEL = IS_MAC ? '⌘' : 'Ctrl+'; export const ALT_LABEL = IS_MAC ? '⌥' : 'Alt+'; diff --git a/ui/src/app/settings.service.ts b/ui/src/app/settings.service.ts index c64f6df..e33bdf7 100644 --- a/ui/src/app/settings.service.ts +++ b/ui/src/app/settings.service.ts @@ -7,7 +7,6 @@ import { RpcService } from './rpc.service'; @Injectable({ providedIn: 'root' }) export class SettingsService { readonly locale = signal(DEFAULT_SETTINGS.locale); - readonly skin = signal(DEFAULT_SETTINGS.skin); readonly theme = signal(DEFAULT_SETTINGS.theme); readonly darkTone = signal(DEFAULT_SETTINGS.darkTone); readonly accent = signal(DEFAULT_SETTINGS.accent); @@ -41,7 +40,6 @@ export class SettingsService { .addEventListener('change', (e) => this.systemDark.set(e.matches)); // the default accent is not the CSS default — apply before first paint this.applyAccent(); - this.applySkin(); if (!this.rpc.available) return; void this.rpc.request.getSettings({}).then((res) => { if (res.ok) this.applyAll(res.settings); @@ -52,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); @@ -65,7 +62,6 @@ export class SettingsService { this.autoClearCompleted.set(settings.autoClearCompleted); this.editor.set(settings.editor); this.editorApp.set(settings.editorApp); - this.applySkin(); this.applyTheme(); this.applyDarkTone(); this.applyAccent(); @@ -79,12 +75,6 @@ export class SettingsService { void this.persist(); } - setSkin(skin: AppSettings['skin']): void { - this.skin.set(skin); - this.applySkin(); - void this.persist(); - } - setTheme(theme: AppSettings['theme']): void { this.theme.set(theme); this.applyTheme(); @@ -152,16 +142,6 @@ export class SettingsService { void this.persist(); } - /** - * The odd one out: a CLASS, not a data-* attribute. 7.scoped.css is - * precompiled to require a literal `.win7` ancestor, so the class is not - * ours to choose. Default ("modern") = class absent, matching the - * delete-when-default rule the other axes follow. - */ - private applySkin(): void { - document.documentElement.classList.toggle('win7', this.skin() === 'win7'); - } - private applyTheme(): void { const theme = this.theme(); if (theme === 'system') { @@ -205,7 +185,6 @@ export class SettingsService { protected settings(): AppSettings { return { locale: this.locale(), - skin: this.skin(), theme: this.theme(), darkTone: this.darkTone(), accent: this.accent(), diff --git a/ui/src/app/tab-bar/tab-bar.ts b/ui/src/app/tab-bar/tab-bar.ts index 3699df1..3297196 100644 --- a/ui/src/app/tab-bar/tab-bar.ts +++ b/ui/src/app/tab-bar/tab-bar.ts @@ -5,12 +5,11 @@ import { LOCALE_LABELS, SUPPORTED } from '@shared/i18n'; import type { AccentTone } from '@shared/types'; import { I18nService } from '../i18n.service'; import { PanelsService } from '../panels.service'; -import { IS_MAC_CHROME, IS_WIN7_CHROME, MOD_LABEL } from '../platform'; +import { MOD_LABEL } from '../platform'; import { RpcService } from '../rpc.service'; import { SettingsService } from '../settings.service'; import { UpdaterService } from '../updater.service'; -const SKINS = ['modern', 'win7'] as const; const THEMES = ['system', 'light', 'dark'] as const; const DARK_TONES = ['dark', 'darker'] as const; // Swatch colors are fixed mid-tones so every dot reads on both light and dark @@ -43,10 +42,7 @@ const CONCURRENCY_CHOICES = [2, 3, 4, 6, 8] as const; traffic lights, and draggable. Elsewhere it's a normal compact row. -->
-
- {{ i18n.t('options.skin.label') }} -
- @for (option of skins; track option) { - - } -
-
- - @if (settings.skin() !== bootSkin) { -

- {{ i18n.t('options.skin.restartHint') }} -

- } -
{{ i18n.t('options.theme.label') }}
@@ -457,14 +429,11 @@ export class TabBar { readonly settingsOpen = signal(false); readonly localeOptions = SUPPORTED; readonly localeLabels = LOCALE_LABELS; - readonly isMacChrome = IS_MAC_CHROME; readonly modLabel = MOD_LABEL; readonly appName = BRANDING.appName; readonly buildLabel = BRANDING.buildLabel; - readonly skins = SKINS; /** What the window was BUILT with, not the current setting — the frameless * chrome only changes on reopen, so the hint compares against this. */ - readonly bootSkin: (typeof SKINS)[number] = IS_WIN7_CHROME ? 'win7' : 'modern'; readonly themes = THEMES; readonly darkTones = DARK_TONES; readonly accents = ACCENTS; diff --git a/ui/src/app/title-bar/title-bar.ts b/ui/src/app/title-bar/title-bar.ts index e0a6e8c..3224e23 100644 --- a/ui/src/app/title-bar/title-bar.ts +++ b/ui/src/app/title-bar/title-bar.ts @@ -1,11 +1,10 @@ import { Component, inject, input, output, signal } from '@angular/core'; -import { IS_WIN7_CHROME } from '../platform'; import { RpcService } from '../rpc.service'; /** - * The win7 skin's Aero title bar. Rendered by all three window kinds, and only - * when `IS_WIN7_CHROME` — under that skin the native frame is gone - * (titleBarStyle 'hidden'), so this row IS the window's chrome. + * The Aero title bar. Rendered by all three window kinds. The native frame is + * gone on every platform (titleBarStyle 'hidden'), so this row IS the window's + * chrome. * * The markup is 7.css's contract, not ours: `.title-bar` > `.title-bar-text` + * `.title-bar-controls` > buttons keyed by `aria-label` (Minimize / Maximize / @@ -21,7 +20,6 @@ import { RpcService } from '../rpc.service'; @Component({ selector: 'app-title-bar', template: ` - @if (enabled) {
{{ title() }}
@@ -35,7 +33,6 @@ import { RpcService } from '../rpc.service';
- } `, }) export class TitleBar { @@ -45,13 +42,12 @@ export class TitleBar { /** Hosts decide what closing means (quit / dirty prompt / managerClose). */ readonly closeRequested = output(); - protected readonly enabled = IS_WIN7_CHROME; protected readonly maximized = signal(false); private readonly rpc = inject(RpcService); constructor() { - if (!this.enabled || !this.rpc.available) return; + if (!this.rpc.available) return; // No maximize event exists, so seed from a one-shot read and then follow // the resize-derived push (which also covers Aero Snap and Win+Up). void this.rpc.request.windowIsMaximized({}).then((res) => { diff --git a/ui/src/index.html b/ui/src/index.html index 4a31566..4a2bbaf 100644 --- a/ui/src/index.html +++ b/ui/src/index.html @@ -13,17 +13,10 @@ attributes are baked in by the bun preload (buildPreload in src/bun/windows.ts) so forced themes are correct at first paint. -->