diff --git a/bun.lock b/bun.lock index 98aa74f..8b8b22a 100644 --- a/bun.lock +++ b/bun.lock @@ -21,6 +21,7 @@ "name": "ui", "version": "0.0.0", "dependencies": { + "7.css": "^0.21.1", "@angular/common": "^22.0.0", "@angular/compiler": "^22.0.0", "@angular/core": "^22.0.0", @@ -45,6 +46,8 @@ "@parcel/watcher", ], "packages": { + "7.css": ["7.css@0.21.1", "", {}, "sha512-Q+/aFUpGemoR/Zltj4a7yvHonyxfb+9cgiwBL+j/Lm+H28EOoLzWoJki7MdGPV+3mLCvKSCVqr3M2x2uNYyeWw=="], + "@algolia/abtesting": ["@algolia/abtesting@1.18.0", "", { "dependencies": { "@algolia/client-common": "5.52.0", "@algolia/requester-browser-xhr": "5.52.0", "@algolia/requester-fetch": "5.52.0", "@algolia/requester-node-http": "5.52.0" } }, "sha512-8siuLG+FIns1AjZ/g2SDVwHz9S+ObacDQISEJvS8XsNei1zl3FXqfqQrBpmrG7ACWCyesXHbicMJtvRbg00FEw=="], "@algolia/client-abtesting": ["@algolia/client-abtesting@5.52.0", "", { "dependencies": { "@algolia/client-common": "5.52.0", "@algolia/requester-browser-xhr": "5.52.0", "@algolia/requester-fetch": "5.52.0", "@algolia/requester-node-http": "5.52.0" } }, "sha512-wtwPgyPmO7b7sQPVgoK29c1VpfS08DnnJCmxX/oU1pV2DlMRJCzQcLN7JSloYpodyKHwM8+9wOzlAM0co3TDmA=="], diff --git a/docs/architecture.md b/docs/architecture.md index 518c827..6537070 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -260,3 +260,74 @@ toolbars deliberately stay on `--background`. Accent tones: one pixel-identical to the old neutral hover. Text-size levels set `--row-h`, `--list-font` etc. per `data-text` attribute, consumed via `h-(--row-h)` / `text-(length:--list-font)` arbitrary utilities. + +### Skins (`settings.skin`: `modern | win7`) + +A fifth theming axis, and the only one that is a **class** (`.win7` on +``) rather than a `data-*` attribute — `7.css/dist/7.scoped.css` is +precompiled against a literal `.win7`, so the name is not ours to pick. +Default `modern` = class absent, matching the delete-when-default rule. + +**Cascade** (`ui/src/styles.css`, first lines): an explicit +`@layer theme, base, components, utilities, skin, skinfix;` statement precedes +both imports. 7.css is imported with `layer(skin)`, i.e. **above `utilities`**, +so a bare `button` rule in 7.css outranks `rounded-md` on that same button — +that inversion is what reskins ~1900 inline utility classes with no template +edits. Consequences: (1) any utility later added to a `button`/`input`/ +`select`/`textarea`/`table` silently loses under the skin; (2) neutralizers +that want the Tailwind value back must sit **inside `@layer skin`**, not +`skinfix` — `revert-layer` rolls back one layer, so from `skin` it lands on +`utilities`, but from `skinfix` it lands back on 7.css and does nothing. +`skinfix` is for rules that state an explicit value (`.w7-btn`). + +**Token bridge** (bottom of styles.css): `:root.win7` re-points the existing +semantic tokens at Win7 values, so utilities Tailwind still controls come out +right too. It adds NO new tokens on purpose — a new colour would need another +`@theme inline` line. The Win7 surface ramp **inverts** the modern one: +`--background` (content panes) is Explorer white and `--chrome` is the darker +`#f0f0f0` window surface. `--radius: 5px` so `rounded-md` +(`calc(--radius - 2px)`, 74 uses) lands exactly on 7.css's 3px `--w7-el-bdr`. +`--primary`/`--ring` are deliberately left alone so the accent axis stays live; +`:root.win7[data-accent]` feeds it to `--w7-w-bg`, which is authentic (Win7 +shipped "Window Color"). Dark Win7 is hand-authored (Microsoft never shipped +one) as ~20 `--w7-*` overrides, duplicated forced + system like every other +dark block. `--w7-font` is restated as a px `font` shorthand so `data-text` +still scales and 7.css's `pt` shorthand stops resetting weight/line-height. + +**Markup hooks** are additive and inert under `modern`: `role="progressbar"` +(+aria) on the transfer bars, `has-scrollbar` on scroll containers (7.css only +styles `::-webkit-scrollbar` under that class), `w7-toolbar` on the three +header rows (their buttons go flat — Explorer's command bar was flat, unlike +dialog push buttons), `w7-btn` on the 10 dialog action buttons. + +**Font**: Segoe UI is absent off Windows and not redistributable. Noto Sans — +the substitute 7.css itself names — is vendored (OFL-1.1, from +`@fontsource/noto-sans`) into `ui/src/fonts/`; under `src/` and not `public/` +so esbuild emits it hashed into `media/` and rewrites the `url()`. + +**Window chrome**: `chromeOptions(skin)` in `src/bun/windows.ts` is the single +place either skin's frame is decided, spread by both the main window and +`openSecondaryWindow`. `win7` uses `titleBarStyle: "hidden"` on **every** +platform — that clears `Titled` from the Cocoa style mask, which is also the +only way to remove the macOS traffic lights. The webview then draws the whole +frame via `ui/src/app/title-bar/title-bar.ts` (7.css's `.title-bar` > +`.title-bar-text` + `.title-bar-controls`, buttons keyed by `aria-label`), +mounted by all three window roots. Because `titleBarStyle` is fixed at +construction, **a skin change only reaches the frame on the next window open**; +the CSS half flips live, and the options row says so. `IS_WIN7_CHROME` in +`platform.ts` reads the preload-baked `__APP_SKIN` (settings resolve too late +for layout) and forces `IS_MAC_CHROME` false, since there are no traffic +lights to reserve `pl-24` for. + +**Window controls**: new `windowMinimize` / `windowToggleMaximize` / +`windowClose` / `windowIsMaximized` requests, built by +`windowControlHandlers(getWindow, onClose?)` in `rpc-handlers.ts` and served by +all three window kinds (each passing a late-bound getter for its own window — +the window takes the rpc in its constructor, same reason `events` is +late-bound). `onClose` is overridden per window kind so the X reuses the +existing close path: editors run their dirty prompt, the manager runs +`managerClose`, main falls through to the cancellable `before-quit` guard. +electrobun emits **no maximize event**, so `trackMaximizeState` derives it from +`resize` + the synchronous `isMaximized()` and pushes `windowState` — which +also covers Aero Snap and Win+Up. Double-click-to-maximize is likewise absent +from electrobun's drag regions and is implemented in the title-bar component. diff --git a/docs/gotchas.md b/docs/gotchas.md index f86eda2..a70dbe2 100644 --- a/docs/gotchas.md +++ b/docs/gotchas.md @@ -28,6 +28,21 @@ touching the adjacent code. - Tailwind v4 canonical class names: `wrap-break-word` not `break-words`, `accent-primary` not `accent-[var(--primary)]`, `z-60` not `z-[60]`. Every color used in a template must resolve through `@theme inline`. +- **The win7 skin layer outranks Tailwind utilities.** 7.css is imported + `layer(skin)`, ordered above `utilities`, so any utility you add to a + `button`/`input`/`select`/`textarea`/`table` is silently dead under that + skin. If it matters, neutralize in the `@layer skin` block at the bottom of + styles.css — and note `revert-layer` only reaches `utilities` from **inside + `skin`**; used from `skinfix` it falls back onto 7.css and is a no-op. This + bit once already. +- `@import … layer(name)` works through `@tailwindcss/postcss` (it wraps the + imported AST), but the **deep path is mandatory**: 7.css ships no `exports` + map and Tailwind's resolver reads only `style`/`mainFields`, so a bare + `@import "7.css"` resolves to nothing. Use `7.css/dist/7.scoped.css`. +- Stylesheet `url()` must resolve at BUILD time: assets referenced from + `styles.css` live under `ui/src/` (esbuild emits them hashed into `media/` + and rewrites the url), NOT `ui/public/` — a public path fails the build with + "Could not resolve". ## Electrobun @@ -56,6 +71,13 @@ touching the adjacent code. attributes before first paint (`buildPreload`), (3) `ui/src/index.html` carries an inline ` diff --git a/ui/src/styles.css b/ui/src/styles.css index 11fba54..51dcbcd 100644 --- a/ui/src/styles.css +++ b/ui/src/styles.css @@ -1,4 +1,23 @@ +/* Layer order — declared BEFORE the imports so it wins (a @layer statement is + one of the only two rule types allowed above @import). `skin` sorts after + `utilities`, so every 7.css rule outranks every Tailwind utility on the + elements it targets; that inversion is what lets the win7 skin restyle + ~1900 inline utility classes without touching the templates. `skinfix` is + above both, and uses `revert-layer` to hand individual properties back to + `utilities` where 7.css's Win7-dialog metrics are wrong for this app. + Tailwind re-declares the first four names, a no-op once ordered here. + See the "Windows 7 skin" section at the bottom of this file. */ +@layer theme, base, components, utilities, skin, skinfix; @import "tailwindcss"; +/* The SCOPED 7.css build: postcss-prefix-selector rewrote every selector to + `.win7` (root rules) or `.win7 ` (the rest), so the whole framework is + inert until SettingsService.applySkin puts that class on . All 61 + icons are data URIs, so nothing needs copying into assets — and NOT the + `7.inline.css` build, which flattens away the --w7-* vars the dark palette + below depends on. The deep path is required: 7.css ships no `exports` map + and Tailwind's resolver only reads `style`/`mainFields`, so a bare + `@import "7.css"` finds no entry. */ +@import "7.css/dist/7.scoped.css" layer(skin); /* shadcn-style theme tokens. Light stays the zinc family; the dark neutrals are a warm gray family (hue ~107) anchored on #1a1a19 — see the dark blocks below. @@ -276,3 +295,300 @@ body { user-select: none; cursor: default; } + +/* ========================================================================== + Windows 7 skin — SettingsService.applySkin puts `.win7` on + ========================================================================== + + Three tiers, and the split matters: + + - `@layer skin` (the 7.css import at the top of this file) owns the + CONTROLS: buttons, inputs, tabs, progress bars, tree views, the Aero + title bar. It sits ABOVE `utilities`, so a bare `button` rule in 7.css + beats `.rounded-md` on that same button. That inversion is the whole + trick — it restyles ~1900 inline utility classes with no template edits. + - This unlayered section owns the BRIDGE: it re-points the app's own + semantic tokens at Win7 values, so utilities Tailwind still controls + (bg-card, border-border, text-muted-foreground) also come out + Win7-correct. It adds NO new tokens on purpose — a new colour token + would need a matching `--color-*` line in `@theme inline`. + - `@layer skinfix` (last, above `skin`) hands individual properties BACK to + the utilities layer with `revert-layer`, for the places where 7.css's + Win7-dialog metrics are wrong for this app. + + WARNING for future edits: because `skin` outranks `utilities`, any Tailwind + utility added later to a