diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..547c90d --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "gpui-native-kit" +version = "0.1.0" diff --git a/docs/PLAN.md b/docs/PLAN.md new file mode 100644 index 0000000..5fc2fe1 --- /dev/null +++ b/docs/PLAN.md @@ -0,0 +1,393 @@ +# gpui-native-kit — Project Plan + +Status: rev 3 · 2026-10-27 + +## Changes in rev 3 (theme engine upgrade) + +- Per-platform adapters read **real semantic system colors** (macOS `NSColor`, Windows WinRT `UISettings`, Linux portal/KDE/GNOME, Omarchy theme files) instead of approximating platform palettes by hand. +- **Accent-ramp engine**: one accent color in → hover/pressed/selected/subtle shades out (stated platform ramps where available, OKLCH derivation elsewhere). +- **Native metrics** promoted to a first-class deliverable (control heights, radii, fonts, focus rings, scrollbars) — color parity alone does not make native feel. +- Platform targets fixed: **macOS, Windows 11 (WinUI), GNOME, KDE, Omarchy, plus a neutral fallback theme** for unknown systems. +- **Custom themes**: sparse TOML definitions with overlay merge priority, config directory + CLI flag, live reload, single-accent override. + +## Changes from rev 1 + +- **Component-by-component porting.** No wholesale vendoring of `gpui-kit` base. Components are copied one at a time, each behind a review gate; the next port starts only after the previous is approved. +- **Custom native theme engine.** No dependency on the `native-theme` crate. We build our own engine *inspired by its design* (sparse theme definition → resolved theme → toolkit projection, presets, accessibility preferences) with our own per-platform readers. +- **Upstream GPUI only.** The only GPUI is `gpui` / `gpui_platform` from `zed-industries/zed` (git, pinned revision). No `gpui-pre`, no `gpui-component`, no `gpui-kit` facade dependency. + +## 1. Vision + +`gpui-native-kit` is a Rust component library for GPUI whose components look native to the user's operating system. + +| Source | Role in this project | +| --- | --- | +| [GPUI Kit `base`](https://gpui-kit.com/base/) | The source we port components from, one per review unit. Its primitives own interaction behavior, controlled state, focus, accessibility semantics — and no visuals. | +| Upstream [GPUI](https://github.com/zed-industries/zed/tree/main/crates/gpui) | The UI framework, from Zed's repository at a pinned revision. | +| [native-theme](https://github.com/tiborgats/native-theme) | **Design inspiration only.** Its layered model (sparse `Theme` → `ResolvedTheme` → connector, presets, accessibility preferences, per-platform readers) is the blueprint for our own engine. We depend on none of its code. | + +The core idea: base primitives own behavior but no visuals. We add the missing layers — a theme engine that reflects the user's system, a projection from resolved theme to base's semantic tokens and native control metrics, and styled components that paint the ported primitives with them. + +**What "native" means here.** Components are rendered by GPUI to *approximate* the platform toolkit's appearance (colors, control heights, radii, focus rings, fonts, light/dark, accent color) — they are not OS-native controls. + +## 2. Background facts (verified) + +- **GPUI install shape** — the only framework dependency: + ```toml + gpui = { git = "https://github.com/zed-industries/zed", rev = "" } + gpui_platform = { git = "https://github.com/zed-industries/zed", rev = "", features = ["font-kit"] } + ``` + (`font-kit` required on macOS for glyph rasterization; Linux needs `wayland` and/or `x11`; Windows needs nothing.) No `gpui-pre`: that package is gpui-component's republishing mechanism for Zed snapshots and plays no role here. Consequence we must manage: source ported from upstream `gpui-base` was written against a particular GPUI snapshot and may differ slightly from our pinned `main` revision — each port unit applies minimal adaptation patches and records them. +- **`gpui-base` facts** (from the official docs): `init` must run once before opening windows; `open_window`-equivalent plumbing wraps the root view in a `Root` (overlay hosting, window state, rem size). Theme tokens: `ColorTokens::light()/dark()` provide semantic palettes — `background`, `foreground`, `surface`, `surface_foreground`, `primary`, `secondary`, `muted`, `accent`, `destructive`, `border`, `input`, `ring`, `selection` + foreground roles — installed as `SemanticThemeTokens` (which also carries radius, spacing, typography, shadow scales) on a theme `Global`. Base paints its own chrome (scrollbars, resize handles) from these tokens. +- **Ported-component catalog** (37 components): Accordion, Alert Dialog, Avatar, Button, Calendar, Checkbox, Collapsible, Color Picker, Combobox, Date Picker, Dialog, Editor, Hover Card, Input, Link, Nav Stack, Number Input, OTP Input, Pagination, Popover, Popup, Progress, Radio, Radio Group, Resizable, Scrollbar, Select, Sheet, Slider, Switch, Table, Tabs, Textarea, Time Field, Toast, Toggle, Toggle Group, Tooltip. Plus infrastructure systems ported lazily when first needed: Motion, Virtual List, Dock, History, Plot, TextView, Text Selection. Upstream examples live in `crates/base/examples` and are the per-component reference behavior we bring along. +- **native-theme design (the blueprint we adapt):** `SystemTheme::from_system()` reads macOS / Windows / KDE / GNOME; a sparse, TOML-shaped `Theme` (Option fields) resolves into a fully populated `ResolvedTheme`; bundled presets; `AccessibilityPreferences` (text scaling, reduce motion, reduce transparency); a thin per-toolkit "connector" maps the resolved theme onto the framework's theming system. Its existing GPUI connector targets gpui-component (139 `ThemeColor` fields, geometry builders, theme-drawn widgets) — useful as a case study, not a dependency. +- **xdg-desktop-portal `Settings` (verified against the spec):** `org.freedesktop.appearance` standardizes `color-scheme` (u), `accent-color` ((ddd) RGB 0–1), `contrast` (u), and `reduced-motion` (u), with a `SettingChanged` signal — one DBus interface covers mode, accent, contrast, and reduce-motion across GNOME, KDE, and generic Linux. +- **Omarchy theme contract (verified against the manual and Omarchy's shipped `tokyo-night` theme):** the active theme is `$HOME/.local/state/omarchy/current/theme/colors.toml` with the theme name in `theme.name` beside it (legacy location `~/.config/omarchy/current`; `current/theme` is a symlink into `~/.config/omarchy/themes` or `/usr/share/omarchy/themes`). `colors.toml` states `mode = "dark" | "light"` plus semantic keys — `accent`, `selection`, `muted`, `background`/`dark_background`/`darker_background`/`lighter_background`, `foreground`/`dark_foreground`/`light_foreground`/`bright_foreground`, and `red`/`yellow`/`orange`/`green`/`cyan`/`blue`/`magenta`/`brown` with `bright_*` variants; the ANSI `color0..15` format is an accepted alternative. +- **Prior art — [gpui-omarchy](https://github.com/huacnlee/gpui-omarchy)** (MIT): a gpui-kit component library styled exclusively for Omarchy. It validates this project's shape (base + system-style layer) and its theme handling is the reference for our Omarchy adapter: read `colors.toml` + `theme.name`, watch filesystem events for palette edits and theme-symlink replacement, `follow_system(cx)` / explicit `apply` to pause/resume following, whole-palette fallback to Tokyo Night when files are missing or invalid. We differ deliberately: upstream zed GPUI only (it uses the published `gpui-kit` facade), per-component porting, and five other platform targets. + +## 3. Architecture + +### 3.1 Workspace layout + +The repo currently holds a single empty package (`gpui-native-kit/`, no dependencies). Phase 0 restructures the repo root into a cargo workspace: + +```text +gpui-native-kit/ # repo root = workspace root +├── Cargo.toml # [workspace] members = ["crates/*"] +├── rust-toolchain.toml # latest stable (gpui requires recent stable) +├── AGENTS.md # adapted coding guidelines (§7) +├── UPSTREAM.md # zed rev pin, per-port-unit provenance & divergence log +├── docs/PLAN.md +├── crates/ +│ ├── base/ # gpui-native-base: foundation seed + ported components +│ │ # (grows one component per approved review unit) +│ ├── theme/ # gpui-native-theme: custom native theme engine + projection +│ └── kit/ # gpui-native-kit: styled native components (facade) +│ # (grows alongside each base port unit) +└── examples/ + └── gallery/ # showcase application; gains a page per port unit +``` + +- The existing empty package becomes `crates/kit` (keeps the `gpui-native-kit` package name). +- `base` and `kit` are separate crates so the "no presentation in base, no behavior in kit" boundary is compiler-enforced. (Decision point D5 covers keeping them separate vs one crate.) + +### 3.2 Dependency direction + +```text +examples/gallery ──▶ gpui-native-kit ──▶ gpui-native-theme ──▶ gpui-native-base ──▶ gpui, gpui_platform (zed, pinned rev) + │ │ + └─────────────────────┴──▶ gpui +``` + +- `base` depends only on GPUI. `theme` depends on GPUI + `base` (it writes base's token global). `kit` depends on all three. One-way, no cycles. +- Nothing depends on `gpui-component`, `gpui-kit`, or `native-theme`. + +### 3.3 Theme pipeline (custom engine, native-theme-inspired) + +```mermaid +graph TD + OS[Adapters: macOS / Windows 11 / GNOME / KDE / Omarchy / fallback] --> ST[SystemTheme normalized snapshot] + PR[ThemeDefinition presets + user overlays] --> RS[resolve] + ST --> RS + RS --> RT[ResolvedTheme + AccessibilityPreferences] + RT --> BR[projection: bridge + metrics] + BR --> TK[SemanticThemeTokens onto base Theme global] + BR --> GM[per-widget native metrics for kit components] + TK --> WIN[windows] + GM --> WIN + EVT[runtime change: gpui appearance events, portal signals, pref changes] -->|re-read, re-resolve, re-install, repaint| BR +``` + +1. **Read** — one adapter per target platform produces a normalized `SystemTheme` (mode, accent, semantic palette, accessibility flags, style hint): + - **macOS**: real `NSColor` semantic colors (`windowBackgroundColor`, `labelColor`, `secondaryLabelColor`, `controlColor`, `controlBackgroundColor`, `separatorColor`, `controlAccentColor`, `selectedTextBackgroundColor`, `keyboardFocusIndicatorColor`, …) read in the current effective appearance via `objc2-app-kit`; contrast/reduce-motion/reduce-transparency from `NSWorkspace` flags; changes via `NSDistributedNotificationCenter` + GPUI appearance events. + - **Windows 11 (WinUI)**: documented WinRT `UISettings.GetColorValue` (`AccentColor`, `AccentLight1–3`, `AccentDark1–3`, `Background`, `Foreground`, …) via the `windows` crate, plus `TextScaleFactor` and `AccessibilitySettings` high-contrast; `Personalize` registry as fallback; runtime via `UISettings.ColorValuesChanged`. + - **GNOME / generic Linux**: `xdg-desktop-portal` `org.freedesktop.appearance` — standardized `color-scheme`, `accent-color` (RGB 0–1), `contrast`, `reduced-motion` with `SettingChanged` (verified against the portal spec) — via `zbus`; `gsettings org.gnome.desktop.interface` as fallback. + - **KDE**: `kdeglobals` ini (`[Colors:Window/Button/Selection/…]`, scheme hash, accent) parsed with fixture tests; portal keys still honored for mode/accent. + - **Omarchy**: read `$HOME/.local/state/omarchy/current/theme/colors.toml` + `theme.name` (legacy `~/.config/omarchy/current`), accepting both the semantic key set and ANSI `color0..15`; watch the `current` dir for palette edits and theme-symlink replacement; invalid or missing palette falls back to Tokyo Night (Omarchy's default); portal keys supply contrast/reduced-motion. + - **Fallback / unknown system**: no adapter applies, or it fails → bundled neutral light/dark theme; also the per-field fallback for values a reader could not supply. +2. **Detect** — capability cascade picks the adapter: Omarchy marker (`~/.local/state/omarchy` or `~/.config/omarchy`) → KDE (`kdeglobals` present) → portal reachable (GNOME/generic) → fallback. macOS/Windows by compile target. +3. **Resolve** — overlay chain: platform defaults → preset (`ThemeDefinition`) → user overlay → accessibility adjustments. Output: `ResolvedTheme` (role palette + control metrics) and `AccessibilityPreferences`. +4. **Project** — `ResolvedTheme` → base's `SemanticThemeTokens` (installed on the theme `Global`, covering base-drawn chrome) + native metrics consumed by `kit` components. +5. **Observe** — a `ThemeWatcher` per adapter forwards real change events (distributed notifications, WinRT `ColorValuesChanged`, portal `SettingChanged`, inotify on KDE/Omarchy files) into the app; GPUI appearance events remain the light/dark baseline everywhere. Each event triggers re-read → re-resolve → re-install → repaint all windows. We are the only writer of base's theme global, which keeps re-application simple. + +### 3.4 Component design rule + +Each `kit` component wraps a ported `base` primitive and owns *only* presentation: + +- The base primitive provides activation, focus, keyboard, dragging, controlled state, AccessKit role. +- The kit component paints track/thumb/indicator/panel etc. from `ResolvedTheme` leaves, with geometry from native metrics. +- No behavior is reimplemented; no color/radius literal appears outside `theme`'s projection and metrics modules. + +## 4. Porting process & component order + +### 4.1 The review gate + +Work proceeds in **port units**. One unit = one component (or one infrastructure system, or the foundation seed). The next unit starts only after you approve the previous one. + +Each unit delivers: + +- the ported module(s) in `crates/base` (behavior, unstyled), copied from upstream `longbridge/gpui-kit` `crates/base` at a recorded upstream commit; +- the styled counterpart in `crates/kit` (from Phase 3 onward — themed at birth; see D9); +- its example wired into the gallery (ported from upstream's example for that primitive, adapted); +- tests (ported and/or new); +- a `UPSTREAM.md` entry: upstream commit, files touched, every adaptation patch made to compile against our zed pin, and any intentional divergence. + +Unit checklist before requesting review: + +- [ ] compiles against the pinned zed rev; `cargo clippy` clean +- [ ] zero visual literals in `base`; all styling reads theme/metrics (kit side) +- [ ] stable domain-derived `ElementId`s; controlled state round-trips through the owner +- [ ] pointer + keyboard + disabled behavior verified by test +- [ ] gallery page renders in light and dark +- [ ] `UPSTREAM.md` provenance recorded + +### 4.2 Port order (dependency-aware) + +**Unit 0 — foundation seed** (prerequisite, not a "component"): `init`, `Root`/window plumbing, theme global with `ColorTokens`/`SemanticThemeTokens`, prelude, small shared utils, gallery shell. + +Then, in order — leaf controls first, popup infrastructure before anchored controls, overlays together, data and pickers last: + +| # | Unit | Notes | +| --- | --- | --- | +| 1 | Button | First full unit; proves the whole pipeline | +| 2 | Checkbox | | +| 3 | Switch | | +| 4 | Radio + Radio Group | One unit | +| 5 | Toggle + Toggle Group | One unit | +| 6 | Link | | +| 7 | Slider | First drag interaction | +| 8 | Progress | | +| 9 | Avatar | | +| 10 | Input | Brings input state machinery | +| 11 | Number Input | Reuses Input | +| 12 | Textarea | | +| 13 | OTP Input | | +| 14 | Popup | Low-level anchored-content host; needed by 15–19 | +| 15 | Popover | | +| 16 | Select | | +| 17 | Combobox | Input + popup | +| 18 | Tooltip | | +| 19 | Hover Card | | +| 20 | Dialog | Overlay focus trap/restore | +| 21 | Alert Dialog | | +| 22 | Sheet | | +| 23 | Toast | May pull in Motion as a lazy infra unit | +| 24 | Tabs | | +| 25 | Accordion | | +| 26 | Collapsible | | +| 27 | Pagination | | +| 28 | Scrollbar | | +| 29 | Resizable | | +| 30 | Table | May pull Virtual List | +| 31 | Tree | May pull Virtual List | +| 32 | Calendar | | +| 33 | Date Picker | Input + popup + Calendar | +| 34 | Time Field | | +| 35 | Color Picker | | +| 36 | Nav Stack | May pull History | + +**Infrastructure systems** (Motion, Virtual List, Dock, History, Plot, TextView, Text Selection) are ported lazily — each as its own review unit — when the first component that needs it comes up (suggestions in the table). Editor is deliberately out of the initial 36; schedule it after Phase 5 if wanted (D12). + +## 5. Milestones + +### Phase 0 — Workspace scaffold + +- Restructure repo root into a cargo workspace (§3.1); move the existing package to `crates/kit`. +- Dependencies: `gpui` + `gpui_platform` from zed at a pinned rev; record the rev in `UPSTREAM.md`. +- Toolchain file, `rustfmt`, `clippy` (deny warnings in CI), license check. +- Write `AGENTS.md` (§7) and the `UPSTREAM.md` skeleton. +- Minimal `main.rs`: open one window with a `div`. +- **Done when**: `cargo run -p gpui-native-kit` shows a window on macOS; CI skeleton (build + clippy + fmt, macOS/Windows/Linux matrix) passes. + +### Phase 1 — Port unit 0: foundation seed + +- Port init/Root/theme-global/tokens/prelude into `crates/base`; gallery shell in `examples/gallery`. +- Default `ColorTokens` palettes only; no system reading yet. +- **Done when**: gallery runs; tokens switchable between light/dark manually; you approve unit 0. + +### Phase 2 — Custom native theme engine (macOS first) + +- `crates/theme`: `ThemeSource` trait + `SystemTheme` / `ThemeDefinition` / `ResolvedTheme` / `AccessibilityPreferences` model; macOS adapter (real `NSColor` semantic reads + accessibility flags); accent-ramp engine (OKLCH); resolve overlay chain; projection to base tokens + the macOS metrics table; install + watcher (distributed notifications + GPUI appearance events); neutral fallback theme; CLI override flag for theme/preset/mode. +- Reader fixture tests (parse canned payloads); resolve/projection unit tests for presets × modes. +- **Done when**: the gallery reflects the live macOS theme; toggling system appearance switches at runtime; a preset can be forced via CLI flag; you approve the engine. + +### Phase 3 — Port units 1–13 (controls + text) + +- Units 1–13 per §4.2, each styled from `ResolvedTheme` at birth, with metrics (control heights, paddings, radii, focus ring) from the engine. +- Per-component UI integration tests; gallery pages. +- **Done when**: gallery shows these controls native-styled in light/dark; all units approved. + +### Phase 4 — Port units 14–27 + platform expansion + +- Popup/overlay/navigation units (14–27), including overlay focus-trap/restore and stacked dismissal verification. +- Parallel track: Windows adapter (WinRT `UISettings` + `ColorValuesChanged`); Linux adapters — portal via `zbus` (baseline), KDE `kdeglobals`, GNOME `gsettings`, Omarchy theme-dir reader (schema verified on a real install); per-platform metrics tables; runtime-change observation on each platform; side-by-side screenshot verification against each toolkit. +- Per-platform CI screenshots for the gallery (best-effort visual regression). +- **Done when**: gallery runs native-themed on macOS, Windows 11, GNOME, KDE, and Omarchy, and falls back cleanly on unknown systems; units approved. + +### Phase 5 — Port units 28–36, lazy infra, polish + +- Data + picker units (28–36); lazy infrastructure units as surfaced (Virtual List, Motion, History). +- Showcase polish (live theme switching UI), README, per-component doc comments with compilable examples, CHANGELOG, test sweep. +- Decide on publishing (crates.io vs git tag). +- **Done when**: all units approved; a fresh contributor can clone, build, run the gallery, and find the docs they need. + +## 6. Theme engine design (custom, `crates/theme`) + +### 6.1 Architecture + +- **`ThemeSource` trait** — one adapter per target platform: `snapshot() -> SystemTheme` plus `watch(callback)` for live changes. Adapters are thin over parseable payloads (registry dumps, ini text, portal replies, theme JSON/CSS) so parsing is fixture-testable on any OS; platform calls live in a small seam. +- **`SystemTheme`** — normalized snapshot: mode (light/dark), accent color, semantic palette (the roles the adapter can supply), high-contrast flag, reduce-motion, reduce-transparency, text-scaling factor, style hint (e.g. `"Breeze"`), platform identity. +- **Detection cascade** — Omarchy marker (`~/.local/state/omarchy` or `~/.config/omarchy`) → KDE (`kdeglobals` present) → portal reachable (GNOME/generic Linux) → fallback theme. macOS/Windows chosen by compile target. +- **`ThemeDefinition`** — sparse, TOML-serializable struct of `Option` fields; used for bundled presets and user themes. May omit almost anything. +- **`ResolvedTheme`** — fully populated: every color role has a value; metrics have platform defaults where the definition is silent. `resolve(system, definitions…, prefs)` is a pure function — unit-testable without a GUI. +- **`AccessibilityPreferences`** — text-scaling factor (≥ 1, clamped finite), reduce-motion, reduce-transparency, high-contrast. Forwarded: text scaling multiplies font sizes (and, through rem, all rem-relative sizing); reduce-motion forwarded to GPUI; reduce-transparency removes overlay backdrops; high-contrast sharpens borders and removes subtle fills. +- **Projection** — `ResolvedTheme` → base `SemanticThemeTokens` + metrics structs consumed by kit components. +- **`ThemeWatcher`** — owns adapter watches, forwards change events to an app entity, drives re-resolve → re-project → repaint (pipeline step 5). + +### 6.2 Initial role mapping (refined during Phase 2) + +| Base role (`ColorTokens`) | `ResolvedTheme` source | +| --- | --- | +| `background` / `foreground` | window background / default text | +| `surface` / `surface_foreground` | card / secondary areas | +| `primary`, `secondary`, `accent` | button + selection colors (macOS: accent-derived) | +| `muted` | muted/secondary text and hover shades | +| `destructive` | danger/error role | +| `border` | button/input border line | +| `input` | text field background/border | +| `ring` | focus ring color | +| `selection` | text selection background (macOS: selected-text color) | + +Rules: every mapping lives in one module; derivations from neighboring roles are documented where the definition is silent; never literals. Tests assert finite, distinct, semantically sane tokens for every preset × mode. + +Omarchy `colors.toml` maps almost 1:1 onto our roles: `background` → `background`, `lighter_background` → `surface`, `dark_background`/`darker_background` → elevated/overlay depth, `foreground` → `foreground`, `dark_foreground` → muted foreground, `selection` → `selection`, `accent` → `primary`/`accent`/`ring`, `red` → `destructive`, `green`/`yellow`/`orange` → success/warning, and the remaining named colors → secondary accents. + +### 6.3 Platform support matrix + +| Target | Detection | Sources read | Live changes | Notes | +| --- | --- | --- | --- | --- | +| macOS | compile target | `NSColor` semantic palette in the effective appearance; `NSWorkspace` accessibility flags | distributed notifications + GPUI appearance events | Real system colors, including custom accent and increased-contrast variants | +| Windows 11 (WinUI) | compile target | WinRT `UISettings` colors (`Accent`, `AccentLight1–3`, `AccentDark1–3`, `Background`, `Foreground`…), `TextScaleFactor`, `AccessibilitySettings` high-contrast; `Personalize` registry fallback | `UISettings.ColorValuesChanged` | Documented WinRT APIs only; gives the exact WinUI token set | +| GNOME | portal reachable + GNOME hint | portal `org.freedesktop.appearance` (`color-scheme`, `accent-color`, `contrast`, `reduced-motion`); `gsettings` fallback | portal `SettingChanged` | Portal keys are the generic-Linux baseline too | +| KDE | `kdeglobals` present | `kdeglobals` color groups + scheme hash + accent; portal for mode/accent | inotify on `kdeglobals` | Ini parser fixture-tested | +| Omarchy | `~/.local/state/omarchy` or `~/.config/omarchy` marker | `current/theme/colors.toml` + `theme.name` (semantic keys or ANSI `color0..15`); portal for contrast/reduced-motion | inotify on the `current` dir (palette edits + symlink replacement) | Invalid/missing palette → Tokyo Night fallback; role mapping in §6.2 | +| Fallback / unknown | none of the above, or reader failure | bundled neutral light/dark theme | manual only | Also the per-field fallback for values a reader could not supply | + +### 6.4 Accent ramp engine + +Every target except the fallback provides one accent color (or a stated ramp). The engine turns an accent into the state shades components need — hover, pressed, selected, disabled, subtle fills: + +- Where the platform **states** shades (WinUI `AccentLight1–3` / `AccentDark1–2`, KDE scheme ramps), use them as-is. +- Where it does not (portal `accent-color`, macOS custom accent beyond the base color), derive shades in **OKLCH**: fixed lightness/chroma offsets toward the background (hover/subtle) or foreground (pressed/strong), hue preserved. +- All derived colors are computed once per resolve, never per frame. + +### 6.5 Native metrics (beyond color) + +Native feel is half geometry. `crates/theme` ships per-platform metric tables consumed by kit components: control heights and paddings, corner radii (WinUI control vs card rounding; macOS control radii), font families and base size (SF Pro / Segoe UI Variable / Adwaita Sans / Noto Sans; rem base), focus-ring policy per platform (macOS keyboard ring, WinUI two-layer ring, GTK outline), scrollbar style (overlay vs inline, widths), popup shadow/elevation, and menu/popup metrics. Values are transcribed from each toolkit's reference metrics and verified with side-by-side screenshots in Phase 2 (macOS) and Phase 4 (Windows/Linux). Prefer GPUI's `.SystemUIFont` resolution with per-platform fallback lists, as gpui-omarchy does, so no fonts need bundling. + +### 6.6 Custom themes + +- `ThemeDefinition` TOML files: bundled presets and user files share one schema. A definition may override any role, any metric, or just the accent (`accent = "#7c3aed"` → full ramp derived). +- Merge priority: platform reader → bundled preset → user file(s) → explicit runtime overrides → accessibility adjustments. `mode = "system" | "light" | "dark"` forces a mode. +- Sources: `--theme ` CLI flag; a config directory (`~/.config/gpui-native-kit/themes/*.toml` or the platform equivalent) scanned at startup; an `apply` API for applications that build their own settings UI. +- Omarchy interop: the loader also accepts Omarchy `colors.toml` theme files (semantic keys or ANSI `color0..15`) directly, so any community Omarchy theme can be dropped into the config dir unchanged. +- Live reload: file watcher (`notify`) re-resolves on save; parse errors surface as a notification, never a panic (per AGENTS.md error rules). +- Follow-mode API (pattern from gpui-omarchy): `follow_system(cx)` keeps adapting to system changes; an explicit `apply` detaches; `system_or_default()` takes a one-time snapshot. + +### 6.7 Implementation vehicles + +Prefer maintained crates over hand-rolled FFI: `objc2` + `objc2-app-kit` (macOS), `windows` (WinRT `UISettings`), `zbus` (portal DBus), `notify` (file watches), `serde` + `toml` (definitions). All behind the `ThemeSource` seam so the engine core stays platform-free and testable. + +### 6.8 What we do differently from native-theme + +- Role-minimal palette aligned 1:1 with base's semantic roles — no 139-field toolkit mapping. +- Real semantic colors per platform (`NSColor` / `UISettings` / portal), not hardcoded approximations of platform palettes. +- Accent-ramp derivation (OKLCH) instead of hand-mapped hover/active shades. +- Native metrics as a first-class deliverable, not an afterthought. +- Omarchy as a first-class target; a designed neutral fallback for unknown systems. +- Custom themes with overlay merge priority and live reload. + +## 7. Coding standards — AGENTS.md adaptation + +Create `AGENTS.md` at the repo root in Phase 0, adapted from the [reya/signed AGENTS.md](https://git.reya.info/reya/signed/src/branch/master/AGENTS.md): + +From reya/signed (kept nearly verbatim): + +- Correctness and clarity first; speed secondary unless specified. +- No organizational/summarizing comments; comments explain non-obvious *why* only; doc comments one line, simple. +- Prefer existing files over new small ones; extend existing `impl` blocks. +- No wrapper structs around dependency APIs; call them directly. +- Bind global handles to a local before use; no inline chained `::global(cx)` calls. +- No `unwrap()`/panicking indexing; propagate with `?`; never silently discard errors with `let _ =` — propagate, `.log_err()`, or handle explicitly. +- Async errors surface to the UI layer with meaningful feedback. +- Full-word names; shadowing to scope clones in async closures. +- GPUI section: context conventions (`cx`/`window`), `Entity`/`WeakEntity` rules, `cx.spawn`/`background_spawn`/`Task` lifetime rules, `Render`/`RenderOnce`, `cx.listener`, `actions!`, `cx.notify()`/`emit`/`subscribe` with stored `Subscription`s, GPUI executor timers in tests instead of `smol::Timer`. +- PR hygiene: imperative title, optional crate prefix, `Release Notes:` section. + +Added for this project (from the GPUI Kit coding guides, adopted as normative): + +- Never invent APIs — search the ported source for real signatures before writing code. +- Behavior/presentation boundary: `crates/base` keeps zero presentation; `crates/kit` keeps zero behavior. +- `RenderOnce` for value-like controls; `Entity<_State>` for retained behavior; stable domain-derived `ElementId`s (never list indexes). +- `SharedString` for retained UI text; private fields + builders/readers for behavioral seams; naming vocabulary (`is_`/`has_` readers, `with_` builders, `on_` callbacks, `State`/`Event`/`Delegate` suffixes). +- No raw colors/radii/px outside `crates/theme`. +- Test at the lowest layer that proves the behavior; regression test before bug fixes. +- **Porting discipline**: port upstream structure and naming as-is; do not "improve" while porting — improvements are separate commits after review; record every divergence in `UPSTREAM.md`. + +## 8. Testing strategy + +1. **Pure tests** — theme resolve overlay chain, projection mapping (presets × modes), reader payload parsing (fixtures), metrics derivations, geometry math (popup clamping, scrollbar ranges). +2. **Context tests** — theme global lifecycle, appearance-change handling, token re-install. +3. **UI integration tests** — headless windows: pointer/keyboard activation, controlled value round-trips, focus movement and traps, disabled/readonly, event order. +4. **Example smoke** — gallery launches, switches theme/preset/mode without panic. + +Conventions: GPUI executor timers in tests; regression test first for bugs; screenshots only for facts the semantic tree cannot express. + +## 9. Licensing & attribution + +- Ported base code: **Apache-2.0** — preserve upstream file headers, keep a per-file provenance table in `UPSTREAM.md` (upstream commit → our path → changes). +- GPUI: Apache-2.0 (dependency, not copied). +- native-theme: inspiration only; no code copied. If that ever changes, respect its Apache/MIT/0BSD terms. +- Docs prose: write our own. Any prose adapted from gpui-kit.com must follow CC BY 4.0 attribution — default to avoiding it. +- This repo's own code: Apache-2.0 recommended (D6). + +## 10. Risks & mitigations + +| Risk | Mitigation | +| --- | --- | +| Zed `main` (pre-1.0) churns; ported upstream source may not match our pin | Pin zed rev; per-unit adaptation patches recorded in `UPSTREAM.md`; CI catches drift early | +| Custom platform readers are a maintenance burden (OS updates, portal API changes) | Thin readers over parseable payloads + fixture tests; graceful fallback chain (system → preset → fallback theme) so a failed read degrades to a sane default | +| Omarchy `colors.toml` key set may evolve across releases | Schema now documented and verified against a shipped theme; parse tolerantly (unknown keys ignored, missing keys fall back); track upstream theme-set changes each release cycle | +| Portal `accent-color` absent on older desktops | Treat as unset (spec-defined); fall back to gsettings/KDE accent, then ramp from a neutral accent | +| Undocumented Windows APIs drift | Use documented WinRT `UISettings` only; registry reads as fallback | +| Accent-color changes may not surface via GPUI events on all platforms | Re-read on window focus/appearance events; verify per platform during Phase 2/4 | +| Review-gated porting is slower than bulk copying | Accepted by design; batches in §4.2/§5 keep the sequence predictable; unit 0 makes everything after it mechanical | +| 36 components + lazy infra is scope creep | Strict one-unit review gate; Editor and Dock deferred unless requested | +| WASM support (upstream base examples run on wasm) is lost | Desktop is the product; theme reading is desktop-only; keep `ColorTokens` fallbacks (D4) | + +## 11. Decision points + +| # | Decision | Status / recommendation | +| --- | --- | --- | +| D1 | Theme source | **Decided (your feedback):** custom engine inspired by native-theme; no `native-theme` dependency | +| D2 | Copy strategy | **Decided (your feedback):** one component per review-gated port unit; no wholesale vendoring | +| D3 | GPUI source | **Decided (your feedback):** upstream zed git only, no `gpui-pre` — pinned rev (recommended) | +| D4 | WASM target | Not initially; desktop only | +| D5 | Separate `base` + `kit` crates vs one crate | Separate (compiler-enforced behavior/presentation boundary) | +| D6 | License for our code | Apache-2.0 | +| D7 | Facade keeps package name `gpui-native-kit` | Yes | +| D8 | Platform order | macOS first (dev machine); then Windows 11 (WinUI), then GNOME → KDE → Omarchy (portal baseline lands with GNOME); fallback theme from day one | +| D9 | Styled counterpart in the same unit as the behavior port | Yes from unit 1 (theme engine lands in Phase 2 before any component unit) | +| D10 | Icon sets (Material/Lucide bundling) | Out of scope initially; revisit after Phase 5 | +| D11 | Preset breadth | Bundled: neutral `fallback` light/dark (unknown systems), `tokyo-night` (Omarchy's default and its Omarchy fallback), plus per-platform presets for testing readers; Omarchy `colors.toml` files load as presets too | +| D12 | Editor primitive | Deferred; schedule after Phase 5 if wanted | +| D13 | Accent state shades | Hybrid: stated platform ramps where available (WinUI, KDE), OKLCH derivation elsewhere | +| D14 | Custom-theme live reload | Yes, via `notify`; the dep is justified by the custom-theme requirement | + +## 12. Immediate next steps + +1. Confirm the remaining recommendations (D4–D12) or adjust. +2. Phase 0: workspace restructure, zed pin, `AGENTS.md`, `UPSTREAM.md` skeleton, CI, window smoke test. +3. Port unit 0 (foundation seed) → your review. +4. Phase 2 theme engine (model + macOS adapter + accent ramp + fallback theme) → your review. +5. Port unit 1 (Button, styled) → your review → continue down the §4.2 list.