Document the core/skins split
architecture.md gains a "UI layout: one core, two skin trees" section — the folder map, the barrel contract that lets main.ts stay ignorant of both trees, why duplicate selectors across trees are safe, and where a new rule belongs (core / a skin / shared tokens). Its "Window kinds" section now describes two-axis root selection, and the "Skins" section is no longer stale prose about a deleted design: it covers the stylesheet split and the two-phase nature of a skin change (CSS live, tree and frame at next window open). gotchas.md gains a section for the split, all of it earned during the work: `.win7` cannot be renamed because 7.scoped.css is precompiled against that literal; reach core through @core/* rather than relative paths, which is what let 67 imports survive two relocations; @font-face url() rebases when a stylesheet moves and fails silently; the @layer statement must stay first; duplicate selectors across trees are safe; and ngtsc type-checks components nothing imports, so a green build does say something about the inactive skin. Also corrected the component paths that moved under skins/, and the updater.service.ts link that moved under core/. Checked every ../ link in docs/ resolves. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
+74
-15
@@ -43,14 +43,18 @@ no-op functions, reassigned in `src/bun/index.ts` after everything exists:
|
||||
|
||||
## Window kinds
|
||||
|
||||
`src/bun/windows.ts` — `buildPreload(mode, ctx?)` injects `__APP_PLATFORM`,
|
||||
`__APP_MODE` (`main|editor|manager`), `__APP_CTX` before any page script.
|
||||
`ui/src/main.ts` picks the root component from `APP_MODE`; all three roots use
|
||||
selector `app-root` (index.html hosts only that tag). `openSecondaryWindow` is
|
||||
the ONLY place that branches platform for secondary chrome: macOS
|
||||
`hiddenInset` + `trafficLightOffset {x:12,y:13}` (the UI renders an
|
||||
`h-10 pl-24` drag-region header via `IS_MAC_CHROME`); other platforms keep the
|
||||
native titlebar and a compact `h-9` non-drag header.
|
||||
`src/bun/windows.ts` — `buildPreload(mode, settings, ctx?)` injects
|
||||
`__APP_PLATFORM`, `__APP_MODE` (`main|editor|manager`), `__APP_SKIN`
|
||||
(`modern|win7`), `__APP_CTX` before any page script. `ui/src/main.ts` selects on
|
||||
BOTH axes: the skin picks a component tree, `APP_MODE` picks a root within it
|
||||
(see "UI layout" below). All roots use selector `app-root` (index.html hosts
|
||||
only that tag). Both globals are preload-baked because both are needed before
|
||||
first paint — settings arrive over an async RPC, far too late to choose a tree
|
||||
without flashing the wrong UI. `chromeOptions(skin)` is the ONLY place the
|
||||
native frame is decided: win7 is frameless + transparent everywhere, modern
|
||||
keeps the native titlebar (macOS `hiddenInset` + `trafficLightOffset
|
||||
{x:12,y:13}`, where the shadlike tree renders an `h-10 pl-24` drag-region header
|
||||
via `IS_MAC_CHROME`).
|
||||
|
||||
`WindowRegistry<Rec>` (same file): keyed window map with dedupe/activate,
|
||||
cascade placement off an anchor frame, focus tracking. electrobun's emitter
|
||||
@@ -58,7 +62,7 @@ has **no `off()`** — `track()` closures capture only `(this, key)` so closed
|
||||
records can be garbage collected. A future "window follows window" grouping
|
||||
belongs here (`anchor.on('move')` + `setPosition`).
|
||||
|
||||
### Editor windows (`src/bun/editor-windows.ts` + `ui/src/app/editor-window/`)
|
||||
### Editor windows (`src/bun/editor-windows.ts` + `ui/src/app/skins/*/editor-window/`)
|
||||
|
||||
One window per file, key `sessionId+path` (sessionId, not fsKey: saves route
|
||||
through a concrete session, and `local` is one shared session anyway).
|
||||
@@ -167,7 +171,41 @@ would float over editor windows).
|
||||
rpc; manager/editors constructed after win; quit guard; focus tracking for
|
||||
notification suppression across all windows).
|
||||
|
||||
## UI services (`ui/src/app/`)
|
||||
## UI layout: one core, two skin trees
|
||||
|
||||
```
|
||||
ui/src/app/
|
||||
core/ app.config.ts, icons.ts, platform.ts, format.ts, file-icons.ts,
|
||||
and all 12 *.service.ts — everything skin-agnostic
|
||||
skins/
|
||||
shadlike/ the modern (shadcn-style) tree + shadlike.css
|
||||
7like/ the Windows 7 tree + 7like.css + fonts/ + title-bar/
|
||||
```
|
||||
|
||||
Each skin owns a **complete** component tree, including its own three window
|
||||
roots, and exports a barrel (`SHAD_ROOTS` / `SEVEN_ROOTS`) with the same
|
||||
`{ main, manager, editor }` shape. `main.ts` therefore knows nothing about
|
||||
either tree's internals: `APP_SKIN` picks the barrel, `APP_MODE` picks the root.
|
||||
Both trees are imported statically into one bundle — the inactive skin costs
|
||||
bundle size but no runtime work, which keeps bootstrap synchronous.
|
||||
|
||||
Duplicate selectors across trees (`app-file-panel` exists in both) are safe:
|
||||
standalone components resolve through their importer's `imports:` array, never
|
||||
a global registry, and the two trees are never mounted together.
|
||||
|
||||
Only `7like` has a `title-bar/`. The modern skin defers to the native frame,
|
||||
which is exactly what `IS_MAC_CHROME` accounts for.
|
||||
|
||||
**Why this split was cheap**: the service layer was already skin-agnostic —
|
||||
`git diff main...feat/7css-skin` touched none of it. Components reach it through
|
||||
the **`@core/*`** tsconfig alias (alongside `@shared/*` → `src/shared`), which
|
||||
is deliberate: skin components sit at varying depths and an alias keeps their
|
||||
imports identical wherever a file moves. Do not replace it with relative paths.
|
||||
|
||||
**Where a new rule goes**: shared behaviour → `core/`; markup and appearance for
|
||||
one look → that skin's folder; tokens both skins consume → `ui/src/styles.css`.
|
||||
|
||||
## UI services (`ui/src/app/core/`)
|
||||
|
||||
- `rpc.service.ts` — wraps `Electroview.defineRPC`. **Push messages fan out
|
||||
through plain callback registries, never signals**: a signal consumed from
|
||||
@@ -277,10 +315,31 @@ A fifth theming axis, and the only one that is a **class** (`.win7` on
|
||||
`<html>`) 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.
|
||||
`SettingsService.applySkin` owns the class once the page is live;
|
||||
`buildPreload` writes it for the first paint.
|
||||
|
||||
**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`**,
|
||||
Unlike the other four axes, this one is not only CSS: it selects a whole
|
||||
component tree (see "UI layout"). So a skin change is **two-phase** —
|
||||
`applySkin` flips the CSS immediately, but the tree and the native frame are
|
||||
both fixed at window construction (`main.ts` reads the preload-baked
|
||||
`__APP_SKIN`; `titleBarStyle`/`transparent` are `BrowserWindow` constructor
|
||||
options). The options row carries the reopen hint as its tooltip.
|
||||
|
||||
**Stylesheet split.** `ui/src/styles.css` holds only what BOTH skins consume —
|
||||
the `@layer` statement, the Tailwind import, OKLCH tokens, `@theme inline`,
|
||||
`data-text` sizes, animations, the accent axis, and the `html, body` base. Each
|
||||
skin's own rules live in its folder (`skins/7like/7like.css`,
|
||||
`skins/shadlike/shadlike.css`) and are `@import`ed from there. `shadlike.css` is
|
||||
near-empty by nature: that skin IS the shared token system plus inline Tailwind.
|
||||
Two constraints: the `@layer` statement must stay the FIRST statement in
|
||||
`styles.css` (it is one of only two rule types allowed above `@import`), and
|
||||
`@font-face` `url()`s resolve relative to the file that declares them — which is
|
||||
why 7like's Noto Sans lives at `skins/7like/fonts/`, inside the skin.
|
||||
|
||||
**Cascade** (`ui/src/styles.css` first lines + `skins/7like/7like.css`): the
|
||||
explicit `@layer theme, base, components, utilities, skin, skinfix;` statement
|
||||
precedes every import. 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`/
|
||||
@@ -290,7 +349,7 @@ that want the Tailwind value back must sit **inside `@layer skin`**, not
|
||||
`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
|
||||
**Token bridge** (`skins/7like/7like.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:
|
||||
@@ -366,7 +425,7 @@ only way to remove the macOS traffic lights. **Linux honours it too**: the GTK
|
||||
wrapper calls `gtk_window_set_decorated(false)` and logs "GTK: Created window
|
||||
without decorations (custom titlebar)" (verified on GNOME/Wayland), so the
|
||||
frameless Aero chrome is not a mac/win-only feature. The webview then draws the whole
|
||||
frame via `ui/src/app/title-bar/title-bar.ts` (7.css's `.title-bar` >
|
||||
frame via `ui/src/app/skins/7like/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**;
|
||||
|
||||
Reference in New Issue
Block a user