Stage 1: make this branch Windows 7 only

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 <noreply@anthropic.com>
This commit is contained in:
2026-08-16 01:45:40 +02:00
co-authored by Claude
parent 72643051f9
commit fe028a9b01
13 changed files with 31 additions and 153 deletions
+2 -2
View File
@@ -86,8 +86,8 @@ const win = new BrowserWindow({
x: 120, x: 120,
y: 120, y: 120,
}, },
// Frame decision (per skin) lives in chromeOptions — see windows.ts. // Frame decision lives in chromeOptions — see windows.ts.
...chromeOptions(currentSettings.skin), ...chromeOptions(),
// lets the UI pick the fused layout + theme before first paint // lets the UI pick the fused layout + theme before first paint
preload: buildPreload("main", currentSettings), preload: buildPreload("main", currentSettings),
rpc, rpc,
+15 -27
View File
@@ -1,7 +1,6 @@
import { BrowserWindow } from "electrobun/bun"; import { BrowserWindow } from "electrobun/bun";
import type { AppSettings } from "../shared/types"; import type { AppSettings } from "../shared/types";
const isMac = process.platform === "darwin";
/** /**
* The preload globals every window gets. Single composition point: the UI * The preload globals every window gets. Single composition point: the UI
@@ -21,15 +20,13 @@ export function buildPreload(
): string { ): string {
let script = let script =
`window.__APP_PLATFORM=${JSON.stringify(process.platform)};` + `window.__APP_PLATFORM=${JSON.stringify(process.platform)};` +
`window.__APP_MODE=${JSON.stringify(mode)};` + `window.__APP_MODE=${JSON.stringify(mode)};`;
`window.__APP_SKIN=${JSON.stringify(settings.skin)};`;
if (ctx !== undefined) script += `window.__APP_CTX=${JSON.stringify(ctx)};`; if (ctx !== undefined) script += `window.__APP_CTX=${JSON.stringify(ctx)};`;
// A class, not a data-* attribute: 7.scoped.css is precompiled to require a // A class, not a data-* attribute: 7.scoped.css is precompiled to require a
// literal `.win7` ancestor (mirrors SettingsService.applySkin). // literal `.win7` ancestor. Unconditional on this branch — the skin is not a
if (settings.skin === "win7") { // setting any more, this build IS the Windows 7 build.
script += `document.documentElement.classList.add("win7");`; script += `document.documentElement.classList.add("win7");`;
}
const dataset: Record<string, string> = {}; const dataset: Record<string, string> = {};
if (settings.theme !== "system") dataset["theme"] = settings.theme; 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 * Native chrome options for a window. The ONE place the frame decision is made —
* frame decision is made — both the main window (src/bun/index.ts) and * both the main window (src/bun/index.ts) and openSecondaryWindow spread this.
* openSecondaryWindow spread this.
* *
* - "win7" draws its own Aero title bar in the webview (drag region + our own * The webview draws its own Aero title bar (drag region + our own
* minimize/maximize/close), so the window must be frameless on EVERY * minimize/maximize/close), so the window is frameless on EVERY platform.
* platform. `titleBarStyle: "hidden"` clears `Titled` from the Cocoa style * `titleBarStyle: "hidden"` clears `Titled` from the Cocoa style mask, which is
* mask, which is also what removes the macOS traffic lights — there is no * also what removes the macOS traffic lights — there is no separate
* separate hide-traffic-lights API. * hide-traffic-lights API. Linux honours it too: the GTK wrapper calls
* - "modern" keeps today's split: macOS fuses the tab bar into an inset * gtk_window_set_decorated(false).
* 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.
*/ */
export function chromeOptions(skin: AppSettings["skin"]) { export function chromeOptions() {
if (skin === "win7") return { titleBarStyle: "hidden" as const }; return { titleBarStyle: "hidden" as const };
return isMac
? { titleBarStyle: "hiddenInset" as const, trafficLightOffset: { x: 12, y: 13 } }
: {};
} }
/** /**
@@ -127,14 +115,14 @@ export interface SecondaryWindowOptions {
/** /**
* App-chromed secondary window. The frame decision is delegated to * 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 { export function openSecondaryWindow(opts: SecondaryWindowOptions): BrowserWindow {
const win = new BrowserWindow({ const win = new BrowserWindow({
title: opts.title, title: opts.title,
url: opts.url, url: opts.url,
frame: opts.frame, frame: opts.frame,
...chromeOptions(opts.settings.skin), ...chromeOptions(),
preload: buildPreload(opts.mode, opts.settings, opts.ctx), preload: buildPreload(opts.mode, opts.settings, opts.ctx),
rpc: opts.rpc, rpc: opts.rpc,
hidden: true, hidden: true,
-6
View File
@@ -37,12 +37,6 @@ export const en = {
title: "Options", title: "Options",
language: "Language", language: "Language",
system: "System", 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" }, theme: { label: "Theme", system: "System", light: "Light", dark: "Dark" },
tone: { label: "Tone", dark: "Dark", darker: "Darker" }, tone: { label: "Tone", dark: "Dark", darker: "Darker" },
accent: { label: "Accent" }, accent: { label: "Accent" },
-8
View File
@@ -78,13 +78,6 @@ export type AccentTone =
export interface AppSettings { export interface AppSettings {
/** UI language; "system" resolves to the OS locale (fallback English). */ /** UI language; "system" resolves to the OS locale (fallback English). */
locale: "system" | SupportedLocale; 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"; theme: "system" | "light" | "dark";
/** Dark appearance: grey ("dark") or near-black AMOLED ("darker"). */ /** Dark appearance: grey ("dark") or near-black AMOLED ("darker"). */
darkTone: "dark" | "darker"; darkTone: "dark" | "darker";
@@ -115,7 +108,6 @@ export interface AppSettings {
export const DEFAULT_SETTINGS: AppSettings = { export const DEFAULT_SETTINGS: AppSettings = {
locale: "system", locale: "system",
skin: "modern",
theme: "system", theme: "system",
darkTone: "dark", darkTone: "dark",
accent: "blue", accent: "blue",
@@ -9,11 +9,6 @@
elsewhere the native titlebar sits above and this is a compact strip --> elsewhere the native titlebar sits above and this is a compact strip -->
<header <header
class="w7-toolbar flex shrink-0 items-center gap-2 border-b border-border bg-chrome pr-2" class="w7-toolbar flex shrink-0 items-center gap-2 border-b border-border bg-chrome pr-2"
[class.h-10]="isMacChrome"
[class.pl-24]="isMacChrome"
[class.electrobun-webkit-app-region-drag]="isMacChrome"
[class.h-9]="!isMacChrome"
[class.pl-3]="!isMacChrome"
> >
<ng-icon name="mynaServers" size="0.95rem" class="shrink-0 text-muted-foreground" /> <ng-icon name="mynaServers" size="0.95rem" class="shrink-0 text-muted-foreground" />
<h1 class="text-sm font-semibold">{{ i18n.t('connMgr.title') }}</h1> <h1 class="text-sm font-semibold">{{ i18n.t('connMgr.title') }}</h1>
@@ -6,7 +6,7 @@ import { ConnectionsService } from '../connections.service';
import { ConfirmDialog } from '../dialogs/confirm-dialog'; import { ConfirmDialog } from '../dialogs/confirm-dialog';
import { PromptDialog } from '../dialogs/prompt-dialog'; import { PromptDialog } from '../dialogs/prompt-dialog';
import { DialogsService } from '../dialogs.service'; import { DialogsService } from '../dialogs.service';
import { blockNativeContextMenu, IS_MAC_CHROME } from '../platform'; import { blockNativeContextMenu } from '../platform';
import { RpcService } from '../rpc.service'; import { RpcService } from '../rpc.service';
import { SettingsService } from '../settings.service'; import { SettingsService } from '../settings.service';
import { TitleBar } from '../title-bar/title-bar'; import { TitleBar } from '../title-bar/title-bar';
@@ -84,7 +84,6 @@ export class ConnectionManager {
readonly i18n = inject(I18nService); readonly i18n = inject(I18nService);
readonly store = inject(ConnectionsService); readonly store = inject(ConnectionsService);
readonly dialogs = inject(DialogsService); readonly dialogs = inject(DialogsService);
readonly isMacChrome = IS_MAC_CHROME;
private readonly rpc = inject(RpcService); private readonly rpc = inject(RpcService);
// injected for its side effects: fetches settings and applies theme/accent/ // injected for its side effects: fetches settings and applies theme/accent/
// text-size data attributes to this window's document root // text-size data attributes to this window's document root
@@ -17,11 +17,6 @@
elsewhere the native titlebar sits above and this is a compact strip --> elsewhere the native titlebar sits above and this is a compact strip -->
<header <header
class="w7-toolbar flex shrink-0 items-center gap-2 border-b border-border bg-chrome pr-2" class="w7-toolbar flex shrink-0 items-center gap-2 border-b border-border bg-chrome pr-2"
[class.h-10]="isMacChrome"
[class.pl-24]="isMacChrome"
[class.electrobun-webkit-app-region-drag]="isMacChrome"
[class.h-9]="!isMacChrome"
[class.pl-3]="!isMacChrome"
> >
<ng-icon name="mynaFileText" size="0.95rem" class="shrink-0 text-muted-foreground" /> <ng-icon name="mynaFileText" size="0.95rem" class="shrink-0 text-muted-foreground" />
<h1 class="min-w-0 truncate font-mono text-sm font-semibold"> <h1 class="min-w-0 truncate font-mono text-sm font-semibold">
+1 -2
View File
@@ -11,7 +11,7 @@ import {
} from '@angular/core'; } from '@angular/core';
import { NgIcon } from '@ng-icons/core'; import { NgIcon } from '@ng-icons/core';
import { I18nService } from '../i18n.service'; 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 { RpcService } from '../rpc.service';
import { SettingsService } from '../settings.service'; import { SettingsService } from '../settings.service';
import { ToastService } from '../toast.service'; import { ToastService } from '../toast.service';
@@ -42,7 +42,6 @@ const DRAFT_DEBOUNCE_MS = 400;
export class EditorWindow { export class EditorWindow {
readonly i18n = inject(I18nService); readonly i18n = inject(I18nService);
readonly ctx = EDITOR_CONTEXT; readonly ctx = EDITOR_CONTEXT;
readonly isMacChrome = IS_MAC_CHROME;
readonly modLabel = MOD_LABEL; readonly modLabel = MOD_LABEL;
readonly altLabel = ALT_LABEL; readonly altLabel = ALT_LABEL;
+1 -22
View File
@@ -8,9 +8,6 @@ declare global {
__APP_PLATFORM?: string; __APP_PLATFORM?: string;
/** Which root component this window boots. */ /** Which root component this window boots. */
__APP_MODE?: 'main' | 'editor' | 'manager'; __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). */ /** Window-kind specific context (EditorOpenContext for editor windows). */
__APP_CTX?: unknown; __APP_CTX?: unknown;
} }
@@ -20,30 +17,12 @@ export const PLATFORM = typeof window !== 'undefined' ? (window.__APP_PLATFORM ?
export const IS_MAC = PLATFORM === 'darwin'; 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_WINDOWS = PLATFORM === 'win32';
export const IS_LINUX = PLATFORM === 'linux'; export const IS_LINUX = PLATFORM === 'linux';
/** Shortcut-hint labels: "⌘W" on macOS, "Ctrl+W" elsewhere. Keyed on the real /** 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 MOD_LABEL = IS_MAC ? '⌘' : 'Ctrl+';
export const ALT_LABEL = IS_MAC ? '⌥' : 'Alt+'; export const ALT_LABEL = IS_MAC ? '⌥' : 'Alt+';
-21
View File
@@ -7,7 +7,6 @@ import { RpcService } from './rpc.service';
@Injectable({ providedIn: 'root' }) @Injectable({ providedIn: 'root' })
export class SettingsService { export class SettingsService {
readonly locale = signal<AppSettings['locale']>(DEFAULT_SETTINGS.locale); 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 theme = signal<AppSettings['theme']>(DEFAULT_SETTINGS.theme);
readonly darkTone = signal<AppSettings['darkTone']>(DEFAULT_SETTINGS.darkTone); readonly darkTone = signal<AppSettings['darkTone']>(DEFAULT_SETTINGS.darkTone);
readonly accent = signal<AppSettings['accent']>(DEFAULT_SETTINGS.accent); readonly accent = signal<AppSettings['accent']>(DEFAULT_SETTINGS.accent);
@@ -41,7 +40,6 @@ export class SettingsService {
.addEventListener('change', (e) => this.systemDark.set(e.matches)); .addEventListener('change', (e) => this.systemDark.set(e.matches));
// the default accent is not the CSS default — apply before first paint // the default accent is not the CSS default — apply before first paint
this.applyAccent(); this.applyAccent();
this.applySkin();
if (!this.rpc.available) return; if (!this.rpc.available) return;
void this.rpc.request.getSettings({}).then((res) => { void this.rpc.request.getSettings({}).then((res) => {
if (res.ok) this.applyAll(res.settings); if (res.ok) this.applyAll(res.settings);
@@ -52,7 +50,6 @@ export class SettingsService {
private applyAll(settings: AppSettings): void { private applyAll(settings: AppSettings): void {
this.locale.set(settings.locale); this.locale.set(settings.locale);
this.skin.set(settings.skin);
this.theme.set(settings.theme); this.theme.set(settings.theme);
this.darkTone.set(settings.darkTone); this.darkTone.set(settings.darkTone);
this.accent.set(settings.accent); this.accent.set(settings.accent);
@@ -65,7 +62,6 @@ export class SettingsService {
this.autoClearCompleted.set(settings.autoClearCompleted); this.autoClearCompleted.set(settings.autoClearCompleted);
this.editor.set(settings.editor); this.editor.set(settings.editor);
this.editorApp.set(settings.editorApp); this.editorApp.set(settings.editorApp);
this.applySkin();
this.applyTheme(); this.applyTheme();
this.applyDarkTone(); this.applyDarkTone();
this.applyAccent(); this.applyAccent();
@@ -79,12 +75,6 @@ export class SettingsService {
void this.persist(); void this.persist();
} }
setSkin(skin: AppSettings['skin']): void {
this.skin.set(skin);
this.applySkin();
void this.persist();
}
setTheme(theme: AppSettings['theme']): void { setTheme(theme: AppSettings['theme']): void {
this.theme.set(theme); this.theme.set(theme);
this.applyTheme(); this.applyTheme();
@@ -152,16 +142,6 @@ export class SettingsService {
void this.persist(); 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 { private applyTheme(): void {
const theme = this.theme(); const theme = this.theme();
if (theme === 'system') { if (theme === 'system') {
@@ -205,7 +185,6 @@ export class SettingsService {
protected settings(): AppSettings { protected settings(): AppSettings {
return { return {
locale: this.locale(), locale: this.locale(),
skin: this.skin(),
theme: this.theme(), theme: this.theme(),
darkTone: this.darkTone(), darkTone: this.darkTone(),
accent: this.accent(), accent: this.accent(),
+2 -33
View File
@@ -5,12 +5,11 @@ import { LOCALE_LABELS, SUPPORTED } from '@shared/i18n';
import type { AccentTone } from '@shared/types'; import type { AccentTone } from '@shared/types';
import { I18nService } from '../i18n.service'; import { I18nService } from '../i18n.service';
import { PanelsService } from '../panels.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 { RpcService } from '../rpc.service';
import { SettingsService } from '../settings.service'; import { SettingsService } from '../settings.service';
import { UpdaterService } from '../updater.service'; import { UpdaterService } from '../updater.service';
const SKINS = ['modern', 'win7'] as const;
const THEMES = ['system', 'light', 'dark'] as const; const THEMES = ['system', 'light', 'dark'] as const;
const DARK_TONES = ['dark', 'darker'] as const; const DARK_TONES = ['dark', 'darker'] as const;
// Swatch colors are fixed mid-tones so every dot reads on both light and dark // 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. --> traffic lights, and draggable. Elsewhere it's a normal compact row. -->
<div <div
class="w7-toolbar relative flex items-center gap-0.5 border-b border-border bg-chrome pr-1.5 electrobun-webkit-app-region-drag" class="w7-toolbar relative flex items-center gap-0.5 border-b border-border bg-chrome pr-1.5 electrobun-webkit-app-region-drag"
[class.h-10]="isMacChrome" class="h-9 pt-1 pl-1.5"
[class.pl-24]="isMacChrome"
[class.pl-1.5]="!isMacChrome"
[class.pt-1]="!isMacChrome"
> >
<div <div
#tabScroll #tabScroll
@@ -181,30 +177,6 @@ const CONCURRENCY_CHOICES = [2, 3, 4, 6, 8] as const;
</select> </select>
</div> </div>
<div class="mt-2.5 flex items-center justify-between gap-2">
<span class="text-xs text-muted-foreground">{{ i18n.t('options.skin.label') }}</span>
<div class="flex rounded-md border border-border p-0.5">
@for (option of skins; track option) {
<button
(click)="settings.setSkin(option)"
class="rounded px-2 py-0.5 text-[11px]"
[class.bg-accent]="settings.skin() === option"
[class.text-foreground]="settings.skin() === option"
[class.text-muted-foreground]="settings.skin() !== option"
>
{{ i18n.t('options.skin.' + option) }}
</button>
}
</div>
</div>
<!-- The CSS half of the skin flips live; the window frame does not
(titleBarStyle is fixed at construction — see chromeOptions). -->
@if (settings.skin() !== bootSkin) {
<p class="mt-1 text-right text-[10px] text-muted-foreground">
{{ i18n.t('options.skin.restartHint') }}
</p>
}
<div class="mt-2.5 flex items-center justify-between gap-2"> <div class="mt-2.5 flex items-center justify-between gap-2">
<span class="text-xs text-muted-foreground">{{ i18n.t('options.theme.label') }}</span> <span class="text-xs text-muted-foreground">{{ i18n.t('options.theme.label') }}</span>
<div class="flex rounded-md border border-border p-0.5"> <div class="flex rounded-md border border-border p-0.5">
@@ -457,14 +429,11 @@ export class TabBar {
readonly settingsOpen = signal(false); readonly settingsOpen = signal(false);
readonly localeOptions = SUPPORTED; readonly localeOptions = SUPPORTED;
readonly localeLabels = LOCALE_LABELS; readonly localeLabels = LOCALE_LABELS;
readonly isMacChrome = IS_MAC_CHROME;
readonly modLabel = MOD_LABEL; readonly modLabel = MOD_LABEL;
readonly appName = BRANDING.appName; readonly appName = BRANDING.appName;
readonly buildLabel = BRANDING.buildLabel; readonly buildLabel = BRANDING.buildLabel;
readonly skins = SKINS;
/** What the window was BUILT with, not the current setting — the frameless /** What the window was BUILT with, not the current setting — the frameless
* chrome only changes on reopen, so the hint compares against this. */ * 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 themes = THEMES;
readonly darkTones = DARK_TONES; readonly darkTones = DARK_TONES;
readonly accents = ACCENTS; readonly accents = ACCENTS;
+4 -8
View File
@@ -1,11 +1,10 @@
import { Component, inject, input, output, signal } from '@angular/core'; import { Component, inject, input, output, signal } from '@angular/core';
import { IS_WIN7_CHROME } from '../platform';
import { RpcService } from '../rpc.service'; import { RpcService } from '../rpc.service';
/** /**
* The win7 skin's Aero title bar. Rendered by all three window kinds, and only * The Aero title bar. Rendered by all three window kinds. The native frame is
* when `IS_WIN7_CHROME` — under that skin the native frame is gone * gone on every platform (titleBarStyle 'hidden'), so this row IS the window's
* (titleBarStyle 'hidden'), so this row IS the window's chrome. * chrome.
* *
* The markup is 7.css's contract, not ours: `.title-bar` > `.title-bar-text` + * The markup is 7.css's contract, not ours: `.title-bar` > `.title-bar-text` +
* `.title-bar-controls` > buttons keyed by `aria-label` (Minimize / Maximize / * `.title-bar-controls` > buttons keyed by `aria-label` (Minimize / Maximize /
@@ -21,7 +20,6 @@ import { RpcService } from '../rpc.service';
@Component({ @Component({
selector: 'app-title-bar', selector: 'app-title-bar',
template: ` template: `
@if (enabled) {
<div class="title-bar electrobun-webkit-app-region-drag" (dblclick)="toggle()"> <div class="title-bar electrobun-webkit-app-region-drag" (dblclick)="toggle()">
<div class="title-bar-text">{{ title() }}</div> <div class="title-bar-text">{{ title() }}</div>
<div class="title-bar-controls electrobun-webkit-app-region-no-drag"> <div class="title-bar-controls electrobun-webkit-app-region-no-drag">
@@ -35,7 +33,6 @@ import { RpcService } from '../rpc.service';
<button aria-label="Close" (click)="closeRequested.emit()"></button> <button aria-label="Close" (click)="closeRequested.emit()"></button>
</div> </div>
</div> </div>
}
`, `,
}) })
export class TitleBar { export class TitleBar {
@@ -45,13 +42,12 @@ export class TitleBar {
/** Hosts decide what closing means (quit / dirty prompt / managerClose). */ /** Hosts decide what closing means (quit / dirty prompt / managerClose). */
readonly closeRequested = output<void>(); readonly closeRequested = output<void>();
protected readonly enabled = IS_WIN7_CHROME;
protected readonly maximized = signal(false); protected readonly maximized = signal(false);
private readonly rpc = inject(RpcService); private readonly rpc = inject(RpcService);
constructor() { 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 // 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). // the resize-derived push (which also covers Aero Snap and Win+Up).
void this.rpc.request.windowIsMaximized({}).then((res) => { void this.rpc.request.windowIsMaximized({}).then((res) => {
+4 -11
View File
@@ -13,17 +13,10 @@
attributes are baked in by the bun preload (buildPreload in attributes are baked in by the bun preload (buildPreload in
src/bun/windows.ts) so forced themes are correct at first paint. --> src/bun/windows.ts) so forced themes are correct at first paint. -->
<style> <style>
html { background: oklch(1 0 0); } /* Four theme states, mirroring the .win7 --background values in
html[data-theme='dark'] { background: oklch(0.2178 0.002 107); } styles.css. The preload adds the class before any page script, so it is
html[data-theme='dark'][data-dark-tone='darker'] { background: oklch(0 0 0); } already on <html> here. Omitting this makes every window flash white
@media (prefers-color-scheme: dark) { before styles.css parses. */
html:not([data-theme='light']) { background: oklch(0.2178 0.002 107); }
html:not([data-theme='light'])[data-dark-tone='darker'] { background: oklch(0 0 0); }
}
/* win7 skin: same four states, mirroring the .win7 --background values
in styles.css. The preload adds this class before any page script, so
it is already on <html> here. Omitting this arm makes every win7
window flash white before styles.css parses. */
html.win7 { background: #ffffff; } html.win7 { background: #ffffff; }
html.win7[data-theme='dark'] { background: #1f1f1f; } html.win7[data-theme='dark'] { background: #1f1f1f; }
html.win7[data-theme='dark'][data-dark-tone='darker'] { background: #121212; } html.win7[data-theme='dark'][data-dark-tone='darker'] { background: #121212; }