Add a toggleable Windows 7 (7.css) skin behind a new skin setting

Evaluation branch: a full Aero skin, including frameless windows with our own
title bar, switchable against the existing modern look.

The app already had a four-axis runtime skinning mechanism and zero component
stylesheets, so the skin is one new setting plus one stylesheet section rather
than a parallel UI. The key move is cascade order: 7.css is imported into a
`skin` layer ABOVE Tailwind's `utilities`, so its bare element rules outrank
the ~1900 inline utility classes already in the templates. Template changes are
limited to additive, skin-inert hooks (ARIA roles, `has-scrollbar`,
`w7-toolbar`, `w7-btn`).

- styles.css: explicit layer order, `7.css/dist/7.scoped.css` via `layer(skin)`,
  a token bridge re-pointing the existing semantic tokens at Win7 values (no new
  tokens, so `@theme inline` is untouched), a hand-authored dark Win7 palette,
  and neutralizers that `revert-layer` 7.css's dialog-button metrics back to the
  utility layer. Neutralizers must live inside `skin`, not `skinfix` —
  `revert-layer` only reaches `utilities` from one layer up.
- Frameless chrome: `chromeOptions(skin)` is now the single place either skin's
  frame is decided. `titleBarStyle: "hidden"` on every platform, which is also
  the only way to drop the macOS traffic lights. New `title-bar` component
  mounted by all three window roots.
- Window controls over RPC (minimize/toggle-maximize/close/is-maximized), served
  per window kind so the X reuses each one's existing close path — editors still
  get their dirty prompt. electrobun has no maximize event, so the state is
  derived from `resize`; double-click-to-maximize is implemented here too.
- Font: Noto Sans vendored (OFL-1.1) under ui/src/fonts, since Segoe UI is
  absent off Windows and not redistributable.

Verified: backend `tsc --noEmit` clean, `bun run build:ui` clean (486 kB initial,
under the 500 kB budget), app launches with the skin active and no console
errors. The rendered result has not been eyeballed — screen capture and
accessibility are both denied to the shell on this machine.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-08-14 15:02:27 +02:00
co-authored by Claude
parent fc03fa6216
commit 07825c1bd1
33 changed files with 828 additions and 48 deletions
+6
View File
@@ -37,6 +37,12 @@ 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" },
+21
View File
@@ -157,6 +157,20 @@ export type AppRPC = {
managerConnect: { params: { id: string }; response: Result };
managerOpenStack: { params: { id: string }; response: Result };
managerClose: { params: Record<string, never>; response: Result };
// custom window chrome (win7 skin — the window is frameless, so the
// webview's title bar owns the controls). Served by every window kind.
windowMinimize: { params: Record<string, never>; response: Result };
/** Maximize/restore in one call; responds with the resulting state. */
windowToggleMaximize: {
params: Record<string, never>;
response: Result<{ maximized: boolean }>;
};
windowClose: { params: Record<string, never>; response: Result };
/** First paint: the title bar needs the state before any resize fires. */
windowIsMaximized: {
params: Record<string, never>;
response: Result<{ maximized: boolean }>;
};
// auto-updater (electrobun Updater)
/** Main window: re-run the update check on demand (menu item). */
checkForUpdate: {
@@ -187,6 +201,13 @@ export type AppRPC = {
editorCloseRequest: Record<string, never>;
/** bun → main: the modal connection manager opened/closed. */
managerState: { open: boolean };
/**
* bun → any window: maximize state changed, so the custom title bar can
* swap its maximize/restore glyph. electrobun has no maximize event —
* this is derived from the window's `resize` event (see windows.ts
* trackMaximizeState), which also covers OS-initiated Aero Snap.
*/
windowState: { maximized: boolean };
/** bun → main: the manager window changed the connection store. */
connectionsChanged: Record<string, never>;
/**
+8
View File
@@ -78,6 +78,13 @@ 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";
@@ -108,6 +115,7 @@ export interface AppSettings {
export const DEFAULT_SETTINGS: AppSettings = {
locale: "system",
skin: "modern",
theme: "system",
darkTone: "dark",
accent: "blue",