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. -->