update plan

This commit is contained in:
2026-10-28 08:54:15 +07:00
parent 0aad422f70
commit 823f4b245c
2 changed files with 281 additions and 316 deletions
+99
View File
@@ -0,0 +1,99 @@
# Coding guidelines
- Prioritize code correctness and clarity. Speed and efficiency are secondary priorities unless otherwise specified.
- Do not write organizational or comments that summarize the code. Comments should only be written to explain "why" the code is written in some way in the case there is a reason that is tricky / non-obvious.
- Keep doc comments and comments simple, one line, straightforward, and unbloated.
- Prefer implementing functionality in existing files unless it is a new logical component. Avoid creating many small files.
- Prefer `impl` blocks over standalone functions: place functionality as methods or associated functions on the relevant type, extending the type's existing `impl` block instead of creating a duplicate one in another file.
- Prefer generic parameters with a `where` clause over argument-position `impl Trait` in function signatures (`fn extend<I>(&mut self, elements: I) where I: IntoIterator<Item = AnyElement>`); return-position `impl Trait` stays.
- Do not wrap dependency APIs in project structs or pass-through helper functions; call their methods directly where needed. Extract a helper only when it holds real logic that is used in more than one place.
- Bind a global store handle to a local before using it (`let theme = Theme::global(cx);` then `theme.…`) instead of chaining `::global(cx)` inline.
- Avoid using functions that panic like `unwrap()`; instead use mechanisms like `?` to propagate errors.
- Be careful with operations like indexing which may panic if the indexes are out of bounds.
- Never silently discard errors with `let _ =` on fallible operations. Always handle errors appropriately:
- Propagate errors with `?` when the calling function should handle them.
- Use `.log_err()` or similar when you need to ignore errors but want visibility.
- Use explicit error handling with `match` or `if let Err(...)` when you need custom logic.
- When implementing async operations that may fail, ensure errors propagate to the UI layer so users get meaningful feedback.
- Avoid creative additions unless explicitly requested.
- Use full words for variable names (no abbreviations like "q" for "queue").
- Use variable shadowing to scope clones in async contexts for clarity, minimizing the lifetime of borrowed references.
## Tests
- Keep only meaningful and critical tests: each test must lock state semantics (activation, state transitions, style priority, a11y mapping) or a non-obvious lifecycle invariant.
- Drop tests that assert mechanical passthroughs, getter/setter round-trips, compile-time API availability, or duplicate the same behavior at a second layer.
## Timers in tests
- In GPUI tests, prefer GPUI executor timers over `smol::Timer::after(...)` for timeouts, delays, or driving `run_until_parked()`:
- Use `cx.background_executor().timer(duration).await` (or `cx.background_executor.timer(duration).await` in `TestAppContext`) so work is scheduled on GPUI's dispatcher.
- Avoid `smol::Timer::after(...)` for test timeouts when you rely on `run_until_parked()`; it may not be tracked by GPUI's scheduler.
# GPUI
GPUI is a UI framework which also provides primitives for state and concurrency management.
## Context
- Context types allow interaction with global state, windows, entities, and system services. They are typically passed as the argument named `cx`. When a function takes callbacks they come after the `cx` parameter.
- `App` is the root context type, providing access to global state and read and update of entities.
- `Context<T>` is provided when updating an `Entity<T>`. It dereferences into `App`, so functions which take `&App` can also take `&Context<T>`.
- `AsyncApp` and `AsyncWindowContext` are provided by `cx.spawn` and `cx.spawn_in` and can be held across await points.
## Window
- `Window` provides access to the state of an application window. It is passed as `window` and comes before `cx` when present.
## Entities
- With `thing: Entity<T>`: `thing.entity_id()`, `thing.downgrade()`, `thing.read(cx)`, `thing.read_with(cx, |thing, cx| ...)`, `thing.update(cx, |thing, cx| ...)`, `thing.update_in(cx, |thing, window, cx| ...)`.
- Within update closures, use the inner `cx` provided to the closure, not the outer one.
- Never update an entity while it is already being updated; this panics.
- `WeakEntity<T>` methods return `anyhow::Result` and fail when the entity no longer exists; treat that as a normal lifecycle outcome. Prefer weak back-references to avoid ownership cycles.
## Concurrency
- All use of entities and UI rendering occurs on a single foreground thread.
- `cx.spawn(async move |cx| ...)` runs on the foreground thread; from a `Context<T>` it looks like `cx.spawn(async move |this, cx| ...)`.
- `cx.background_spawn(async move { ... })` runs work on other threads.
- Both return a `Task<R>`; dropping it cancels the work. Prevent cancellation by awaiting, detaching deliberately, or storing the task in a field.
- Prefer keeping a task in a field over `.detach()` when the work belongs to a view or store; a detached task outlives its owner. A finished `Task` held in a container is not reaped — drop finished handles or hold a single `Option<Task<_>>`.
## Rendering and state
- `Render` builds an element tree from entity state; `RenderOnce` (with `#[derive(IntoElement)]`) is for value-like components whose `render` takes `self` and `&mut App`.
- Keep `render` declarative; use `.when(...)` / `.when_some(...)` for small conditional refinements.
- Controlled components: the owner passes the current value, callbacks report intent, the owner updates state and calls `cx.notify()`. No second source of truth.
- Call `cx.notify()` after a mutation that changes rendering; notify the narrowest owning entity. Never notify from `render`.
- Use `cx.emit(...)` for semantic events (declare `impl EventEmitter<EventType>`), `cx.subscribe(...)` / `cx.observe(...)` for reactions, and store returned `Subscription`s in the owner (commonly `_subscriptions: Vec<Subscription>`).
- Use stable domain-derived `ElementId`s (`("delete-project", project.id)`); never list indexes or freshly generated random IDs for reorderable content.
- `cx.listener(...)` adapts event handlers to update the current entity.
- Actions: `actions!(namespace, [Action])` for data-free actions, the `Action` derive otherwise; register `on_action` on the focused region with a matching `key_context`.
- Retain a `FocusHandle` in the entity that owns keyboard interaction; a tracked handle is a Tab stop only when built with `tab_stop(true)`.
# Project rules
- Dependency direction is one-way: `gpui-native-kit` (facade) → `gpui-native-core` / `gpui-native-editor` / … → `gpui-native-theme` + `gpui-native-icons` → gpui. No cycles. Nothing depends on `gpui-component`, `gpui-kit`, `gpuikit`, `bezel`, `native-theme`, or any gpui fork.
- `crates/theme` owns the native theme engine and every color, radius, spacing, and metric value — no raw `rgb`/`rgba`/`hsla` or layout `px(...)` literals anywhere else.
- `crates/icons` owns icon resolution; components reference icons by semantic role — no glyph names, unicode symbols, or SVG paths anywhere else.
- `crates/core` and its sibling crates own components: each one is a small self-contained implementation gated behind its own cargo feature. Features are additive, gate whole modules only, and may depend on other features. Behavior and presentation live together. No separate behavior layer — promote a shared helper only when a third component needs it.
- Components are written in-tree. Other UI toolkits are read for API ideas only; never port their code or depend on them.
- gpui/gpui_platform come from zed's repository at the rev pinned in `docs/PLAN.md`; bump the pin deliberately and re-verify.
- `SharedString` for UI text retained across frames; `String` only for mutable buffers.
- Behavioral seams use private fields + builders/readers. Boolean readers are `is_`/`has_`; general builders use `with_`; in-place mutation uses `set_`; callbacks use `on_`; retained models end in `State`; pluggable owners in `Delegate`/`Provider`.
- Never invent an API: search the gpui source for the real signature before writing code.
- Prefer `.SystemUIFont` and rem-based helpers (`p_2()`, `text_sm()`, `gap_3()`) over pixel literals.
# Pull request hygiene
- Use a clear, correctly capitalized, imperative PR title (for example, `fix crash in project panel`).
- Avoid conventional commit prefixes in PR titles (`fix:`, `feat:`, `docs:`, etc.).
- Avoid trailing punctuation in PR titles.
- Optionally prefix the title with a crate name when one crate is the clear scope (for example, `kit: add button port unit`).
- Include a `Release Notes:` section as the final section in the PR body, with one bullet:
- `- Added ...`, `- Fixed ...`, or `- Improved ...` for user-facing changes, or `- N/A` otherwise.
Release Notes:
- N/A
+182 -316
View File
@@ -1,88 +1,103 @@
# gpui-native-kit — Project Plan # gpui-native-kit — Project Plan
Status: rev 3 · 2026-10-27 Rev 5 (2026-10-28). Changes from rev 4: the workspace is split into focused crates — `theme`, `icons`, `core`, and sibling crates for advanced elements (`editor`, `data`, …) — with per-component cargo features so applications enable only what they use. System icons are now in scope: semantic roles resolved to SF Symbols / Segoe Fluent Icons / freedesktop icons per platform, with a bundled Lucide fallback. Everything else (empty-project reset, own lean implementations, theme engine as the product) stands.
## 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 ## 1. Vision
`gpui-native-kit` is a Rust component library for GPUI whose components look native to the user's operating system. GPUI components that feel native to the OS they run on — macOS, Windows 11 (WinUI), GNOME, KDE, Omarchy — with a designed neutral fallback for unknown systems. Every color, radius, spacing, metric, and icon comes from engines that read the running system or a user-supplied theme. The theme and icon engines are the product; components are their proof.
| Source | Role in this project | Consumption is flexible: small focused crates, per-component cargo features, and a thin optional facade. A hello-world app can depend on `gpui-native-core` with two features; a full app can depend on the `gpui-native-kit` facade with `--all-features`.
| --- | --- |
| [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. ## 2. Evaluated foundations (2026-10-28)
**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. | Crate | What it is | Verdict | What we take |
| --- | --- | --- | --- |
| gpui-kit (longbridge) | Unstyled `base` behavior layer + styled component library | Dropped: over-engineered base; review-gated porting too slow; upstream pins `gpui-pre` snapshots | Nothing; previous ports reverted |
| gpuikit (iamnbutler) | Web/SwiftUI-style toolkit (editor, input, keymap, markdown, selection, a11y modules) on `gpui-unofficial` | Not a foundation: builds on the `gpui-unofficial` crates.io distribution (conflicts with D3); generic theme system with no per-OS native look | Component API ergonomics; showcase/gallery pattern |
| bezel (crabtalk) | SwiftUI-lean library; style flows through the environment — `Theme::of(cx)` + catalog traits, zero style parameters, `Brand`/`Tint` hue re-theming; own gpui fork (`bezel-gpui`) | Not a foundation: gpui fork (conflicts with D3); one visual language (macOS-leaning HIG), not per-OS native | Environment-first styling: components read the theme global at paint time and take no color/size parameters; `Brand`-style hue overrides inform custom themes |
| zeron (zeronsh) | Production gpui app (coding-agent control plane; crates: ui, theme, text, syntax, term, …) | Not a foundation — an application, not a library | Existence proof that gpui scales; app-level patterns for data-heavy screens |
## 2. Background facts (verified) Decision D15: we depend on no UI toolkit and port nothing. These crates (plus native-theme, D1) are read for API and design ideas only — and always simplified, never mirrored 1:1. When in doubt, ship the smaller API.
## 3. Background facts (verified)
- **GPUI install shape** — the only framework dependency: - **GPUI install shape** — the only framework dependency:
```toml ```toml
gpui = { git = "https://github.com/zed-industries/zed", rev = "<pin>" } gpui = { git = "https://github.com/zed-industries/zed", rev = "ec86047d67acd6fddd81392f44ed6ebcc6e7f531" }
gpui_platform = { git = "https://github.com/zed-industries/zed", rev = "<pin>", features = ["font-kit"] } gpui_platform = { git = "https://github.com/zed-industries/zed", rev = "ec86047d67acd6fddd81392f44ed6ebcc6e7f531", 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. `font-kit` is required on macOS for glyph rasterization; Linux needs `wayland` and/or `x11`; Windows needs nothing. No `gpui-pre`, no forks, no `gpui-unofficial`. Toolchain 1.98.1 via `rust-toolchain.toml` (matches zed at the pinned rev; verified building on this machine). Bumping the pin is a deliberate act: re-verify, fix drift, note it in the bump commit message.
- **`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. - **native-theme design (the blueprint we adapt, not a dependency):** sparse TOML-shaped `Theme` (Option fields) resolves into a fully populated `ResolvedTheme`; bundled presets; `AccessibilityPreferences`; thin per-toolkit connectors. Its GPUI connector targets gpui-component — a case study only.
- **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. - **xdg-desktop-portal `Settings` (verified against the spec):** `org.freedesktop.appearance` standardizes `color-scheme` (u), `accent-color` ((ddd) RGB 0–1), `contrast` (u), `reduced-motion` (u), with `SettingChanged` — one DBus interface covering mode, accent, contrast, and reduce-motion across GNOME, KDE, and generic Linux.
- **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. - **Omarchy theme contract (verified against the manual and the shipped `tokyo-night` theme):** active theme is `$HOME/.local/state/omarchy/current/theme/colors.toml` with `theme.name` beside it (legacy `~/.config/omarchy/current`; `current/theme` is a symlink into the theme set). `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_*`; the ANSI `color0..15` format is an accepted alternative.
- **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. - **Platform icon systems:** macOS SF Symbols — thousands of template glyphs, runtime lookup via `NSImage(systemSymbolName:)` rendered at a requested size/weight; Apple's license forbids redistributing the set, so it is used at runtime only, never bundled. Windows 11 Segoe Fluent Icons — a system font addressed by codepoint. Linux (and Omarchy) — the freedesktop icon-theme spec: lookup across the active theme (Breeze, Adwaita) and hicolor. Lucide (ISC) is the bundled offline fallback everywhere else.
- **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 (huacnlee, MIT):** validates the Omarchy adapter design: read `colors.toml` + `theme.name`, watch filesystem events for palette edits and theme-symlink replacement, `follow_system` / explicit `apply` to pause/resume following, whole-palette fallback to Tokyo Night when files are missing or invalid.
- **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 ## 4. Architecture
### 3.1 Workspace layout ### 4.1 Workspace layout (Phase A)
The repo currently holds a single empty package (`gpui-native-kit/`, no dependencies). Phase 0 restructures the repo root into a cargo workspace:
```text ```text
gpui-native-kit/ # repo root = workspace root gpui-native-kit/ # repo root = workspace root
├── Cargo.toml # [workspace] members = ["crates/*"] ├── Cargo.toml # [workspace]; root package = facade
├── rust-toolchain.toml # latest stable (gpui requires recent stable) ├── rust-toolchain.toml
├── AGENTS.md # adapted coding guidelines (§7) ├── AGENTS.md
├── UPSTREAM.md # zed rev pin, per-port-unit provenance & divergence log
├── docs/PLAN.md ├── docs/PLAN.md
├── src/lib.rs # gpui-native-kit: thin facade (re-exports, feature-driven)
├── crates/ ├── crates/
│ ├── base/ # gpui-native-base: foundation seed + ported components │ ├── theme/ # gpui-native-theme: the native theme engine
│ │ # (grows one component per approved review unit) │ ├── icons/ # gpui-native-icons: system icons + bundled fallback
│ ├── theme/ # gpui-native-theme: custom native theme engine + projection │ ├── core/ # gpui-native-core: components every app needs
│ └── kit/ # gpui-native-kit: styled native components (facade) │ │ # (one cargo feature per component)
│ # (grows alongside each base port unit) │ └── editor/ # gpui-native-editor: created when wave 2 needs it (D12)
└── examples/ └── examples/
└── gallery/ # showcase application; gains a page per port unit └── gallery/ # showcase; a page per component + theme/icon inspector
``` ```
- The existing empty package becomes `crates/kit` (keeps the `gpui-native-kit` package name). Advanced crates (`data` for Table/Tree/List, `charts`, `pickers`) are created on demand when their wave starts — not speculative scaffolding.
- `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 ```mermaid
graph TD
```text FAC[gpui-native-kit facade — optional] --> CORE[gpui-native-core]
examples/gallery ──▶ gpui-native-kit ──▶ gpui-native-theme ──▶ gpui-native-base ──▶ gpui, gpui_platform (zed, pinned rev) FAC --> FUT[editor / data / …]
│ │ CORE --> T[gpui-native-theme]
└─────────────────────┴──▶ gpui CORE --> I[gpui-native-icons]
T --> P[gpui + gpui_platform — zed, pinned rev]
I --> P
``` ```
- `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. One-way, no cycles. Nothing depends on `gpui-component`, `gpui-kit`, `gpuikit`, `bezel`, or `native-theme`. Applications may depend on `gpui-native-core` directly and skip the facade.
- Nothing depends on `gpui-component`, `gpui-kit`, or `native-theme`.
### 3.3 Theme pipeline (custom engine, native-theme-inspired) ### 4.2 Feature model
- **One feature per component** in `core` (`button`, `checkbox`, `switch`, `radio`, `toggle`, `link`, `slider`, `progress`, `avatar`, `input`, `popover`, `tooltip`, `menu`, `dialog`, `toast`, `list`, `tabs`, …). `default = ["full"]`; lean apps use `default-features = false` + the features they want.
- Features **gate whole modules only** (`#[cfg(feature = "…")] pub mod button;`) and are additive. A component that composes another declares the dependency in Cargo.toml (`dialog = ["button"]`). No cfg logic may cross modules.
- Facade features map to crates: `core` (default), `editor`, `data`, …
- Shared helpers promoted per D16 live in an ungated `common` module so every feature combination sees them.
- CI checks three points, not a full matrix: default, `--no-default-features`, `--all-features`, plus a compile check of each feature alone.
### 4.3 Component design
- **Controlled components:** the owner passes the current value, callbacks report intent, the owner updates state and calls `cx.notify()`. No second source of truth.
- **Value-like controls** are `#[derive(IntoElement)]` + `RenderOnce`; stateful ones are `Entity<XxxState>` the application owns.
- **No style parameters** (bezel's model): a component reads the theme global at paint time and takes zero color/size arguments. Overrides are gpui style modifiers chained by the caller.
- **Behavior is inline.** Activation, focus, keyboard, dragging, and a11y are written per component with gpui primitives (`Interactivity`, `FocusHandle` + `tab_stop(true)`, `actions!`, a11y builders). A helper is promoted to shared `common` only when a third component needs it — no pre-built behavior layer.
- **APIs are our own**, shaped by what our components need; inspiration from other crates is distilled, never copied 1:1.
- Each component has a named native reference per platform (NSButton / WinUI Button / Adwaita / Breeze) recorded on its gallery page; "native feel" is reviewed against those references.
- Naming seams per AGENTS.md: `is_`/`has_` readers, `with_` builders, `set_` mutation, `on_` callbacks, `State`/`Delegate` suffixes, stable domain-derived `ElementId`s.
### 4.4 Icons (`crates/icons`)
- **`IconRole`** — a semantic enum of the glyphs UI actually needs (~25 to start): chevrons, check, close, plus/minus, search, folder, warning/info/success, copy, arrow-left/right, calendar, clock, settings, dots-horizontal, … Components and apps reference roles, never glyph names.
- **Resolver cascade per platform**, behind one `Icon` element:
- macOS: SF Symbols via `NSImage(systemSymbolName:)`, rasterized through AppKit at (size, weight, appearance, tint) and cached; never bundled.
- Windows: Segoe Fluent Icons codepoints rendered as text (system font).
- Linux/Omarchy: freedesktop icon-theme lookup (active theme → hicolor), SVG painted via gpui's `svg()`.
- Fallback everywhere: a bundled Lucide subset (ISC; attribution in NOTICE) — ships first so components work before platform resolvers land.
- Public API stays tiny: `icon(role)` → element with `.size(...)`; tint defaults to theme tokens. A missing glyph logs once and falls back — never panics.
- Gallery page: icon browser showing every role resolved on the current platform.
### 4.5 Theme pipeline
```mermaid ```mermaid
graph TD graph TD
@@ -90,304 +105,155 @@ graph TD
PR[ThemeDefinition presets + user overlays] --> RS[resolve] PR[ThemeDefinition presets + user overlays] --> RS[resolve]
ST --> RS ST --> RS
RS --> RT[ResolvedTheme + AccessibilityPreferences] RS --> RT[ResolvedTheme + AccessibilityPreferences]
RT --> BR[projection: bridge + metrics] RT --> TK[token projection onto the Theme global]
BR --> TK[SemanticThemeTokens onto base Theme global] TK --> WIN[all windows repaint]
BR --> GM[per-widget native metrics for kit components] EVT[runtime change events] -->|re-read, re-resolve, re-install| TK
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): 1. **Read** — one adapter per 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. 2. **Detect** — capability cascade: Omarchy marker → KDE (`kdeglobals` present) → portal reachable (GNOME/generic Linux) → fallback. macOS/Windows by compile target.
- **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`. 3. **Resolve** — overlay chain: platform reader → bundled preset → user theme → runtime overrides → accessibility adjustments. Pure function, unit-testable without a GUI.
- **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. 4. **Project** — `ResolvedTheme` → the token structs components consume, installed on the theme `Global`.
- **KDE**: `kdeglobals` ini (`[Colors:Window/Button/Selection/…]`, scheme hash, accent) parsed with fixture tests; portal keys still honored for mode/accent. 5. **Observe** — `ThemeWatcher` forwards real change events (distributed notifications, WinRT `ColorValuesChanged`, portal `SettingChanged`, inotify on KDE/Omarchy files); each event re-runs read → resolve → project → repaint.
- **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 ## 5. Theme engine (`crates/theme`)
Each `kit` component wraps a ported `base` primitive and owns *only* presentation: ### 5.1 Architecture
- The base primitive provides activation, focus, keyboard, dragging, controlled state, AccessKit role. - **`ThemeSource` trait** — one adapter per platform: `snapshot() -> SystemTheme` plus `watch(callback)`. Adapters are thin over parseable payloads (ini text, registry dumps, portal replies) so parsing is fixture-testable on any OS.
- The kit component paints track/thumb/indicator/panel etc. from `ResolvedTheme` leaves, with geometry from native metrics. - **`SystemTheme`** — normalized snapshot: mode, accent, semantic palette, high-contrast, reduce-motion, reduce-transparency, text-scaling, style hint, platform identity.
- No behavior is reimplemented; no color/radius literal appears outside `theme`'s projection and metrics modules. - **`ThemeDefinition`** — sparse TOML-serializable `Option`-field struct; bundled presets and user themes share one schema.
- **`ResolvedTheme`** — fully populated; `resolve(system, definitions…, prefs)` is pure.
- **`AccessibilityPreferences`** — text scaling multiplies font sizes (through rem, all rem-relative sizing); reduce-motion forwards to GPUI; reduce-transparency drops overlay backdrops; high-contrast sharpens borders and removes subtle fills.
- **`ThemeWatcher`** — owns adapter watches, drives the observe step.
## 4. Porting process & component order ### 5.2 Role mapping
### 4.1 The review gate | Token role | Source |
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 | | `background` / `foreground` | window background / default text |
| `surface` / `surface_foreground` | card / secondary areas | | `surface` / `surface_foreground` | cards / secondary areas |
| `primary`, `secondary`, `accent` | button + selection colors (macOS: accent-derived) | | `primary`, `secondary`, `accent` | button + selection colors (macOS: accent-derived) |
| `muted` | muted/secondary text and hover shades | | `muted` | secondary text and hover shades |
| `destructive` | danger/error role | | `destructive` | danger role |
| `border` | button/input border line | | `border` / `input` | control border line / field background |
| `input` | text field background/border | | `ring` | focus ring |
| `ring` | focus ring color | | `selection` | text selection background |
| `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: `lighter_background` → `surface`, `dark_background`/`darker_background` → elevation depth, `dark_foreground` → muted foreground, `accent` → `primary`/`accent`/`ring`, `red` → `destructive`, `green`/`yellow`/`orange` → success/warning. Every mapping lives in one module; 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. ### 5.3 Platform matrix
### 6.3 Platform support matrix | Target | Detection | Sources read | Live changes |
| --- | --- | --- | --- |
| macOS | compile target | `NSColor` semantic palette (`windowBackgroundColor`, `labelColor`, `controlColor`, `controlAccentColor`, `selectedTextBackgroundColor`, `keyboardFocusIndicatorColor`, …) in the effective appearance via `objc2-app-kit`; `NSWorkspace` accessibility flags | distributed notifications + GPUI appearance events |
| Windows 11 (WinUI) | compile target | WinRT `UISettings` (`AccentColor`, `AccentLight1–3`, `AccentDark1–2`, `Background`, `Foreground`), `TextScaleFactor`, `AccessibilitySettings` high-contrast; `Personalize` registry fallback | `UISettings.ColorValuesChanged` |
| GNOME / generic Linux | portal reachable | portal `org.freedesktop.appearance` (`color-scheme`, `accent-color`, `contrast`, `reduced-motion`); `gsettings` fallback | portal `SettingChanged` |
| KDE | `kdeglobals` present | `kdeglobals` color groups + scheme hash + accent; portal for mode/accent | inotify on `kdeglobals` |
| 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) |
| Fallback / unknown | none of the above, or reader failure | bundled neutral light/dark theme | manual only |
| Target | Detection | Sources read | Live changes | Notes | ### 5.4 Accent ramp engine
| --- | --- | --- | --- | --- |
| 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 supplies one accent color (or a stated ramp). Where the platform states shades (WinUI `AccentLight1–3`/`AccentDark1–2`, KDE scheme ramps) use them as-is; elsewhere derive the state shades components need (hover, pressed, selected, disabled, subtle fills) in OKLCH with fixed lightness/chroma offsets toward the background or foreground, hue preserved. Derived colors compute once per resolve, never per frame.
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: ### 5.5 Native metrics
- Where the platform **states** shades (WinUI `AccentLight1–3` / `AccentDark1–2`, KDE scheme ramps), use them as-is. Native feel is half geometry. Per-platform tables for: control heights and paddings, corner radii (WinUI control vs card rounding; macOS control radii), font fallback lists resolved through `.SystemUIFont` (SF Pro / Segoe UI Variable / Adwaita Sans / Noto Sans — no fonts bundled), focus-ring policy (macOS keyboard ring, WinUI two-layer ring, GTK outline), scrollbar style (overlay vs inline), popup elevation and menu metrics. Values are transcribed from each toolkit's reference metrics and verified with side-by-side screenshots (macOS in Phase B, the rest in Phase F).
- 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) ### 5.6 Custom themes
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. - `ThemeDefinition` TOML: presets and user files share one schema; may override any role or metric, or just the accent (`accent = "#7c3aed"` → full derived ramp).
- Merge priority: platform reader → preset → user file(s) → runtime overrides → accessibility adjustments. `mode = "system" | "light" | "dark"` forces a mode.
- Sources: `--theme <preset|path>` flag, a config directory scanned at startup, and an `apply` API for in-app settings UIs.
- Omarchy interop: the loader accepts Omarchy `colors.toml` (semantic or ANSI) directly, so community themes drop in unchanged.
- Live reload via `notify`; parse errors surface as a notification, never a panic.
- Follow-mode API: `follow_system(cx)` keeps adapting to system changes; explicit `apply` detaches; `system_or_default()` takes a one-time snapshot.
### 6.6 Custom themes ### 5.7 Implementation vehicles
- `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). `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.
- Merge priority: platform reader → bundled preset → user file(s) → explicit runtime overrides → accessibility adjustments. `mode = "system" | "light" | "dark"` forces a mode.
- Sources: `--theme <preset|path>` 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 ### 5.8 What we do differently from native-theme
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. Real semantic colors per platform (`NSColor` / `UISettings` / portal) instead of hardcoded approximations; OKLCH accent-ramp derivation; native metrics as a first-class deliverable; Omarchy as a first-class target; a designed neutral fallback; custom themes with overlay merge priority and live reload; a role-minimal token set instead of a 139-field toolkit mapping.
### 6.8 What we do differently from native-theme ## 6. Component roadmap (written from scratch)
- Role-minimal palette aligned 1:1 with base's semantic roles — no 139-field toolkit mapping. | Wave | Crate | Components |
- 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. | 1 — controls | core | Button, Checkbox, Switch, Radio + RadioGroup, Toggle, Link, Slider, Progress, Avatar; plus the `IconRole` catalog + Lucide fallback |
- Native metrics as a first-class deliverable, not an afterthought. | 2 — input | core | Input (single-line first, then multiline), NumberInput. String buffer + `unicode-segmentation` word movement; the field owns its own selection. Multiline editing may seed `crates/editor`. |
- Omarchy as a first-class target; a designed neutral fallback for unknown systems. | 3 — overlays | core | Popover, Tooltip, Menu, Dialog, AlertDialog, Sheet, Toast |
- Custom themes with overlay merge priority and live reload. | 4 — data & navigation | data (new) | List, Table, Tree, Tabs, Sidebar |
| 5 — pickers | pickers or data (decide at wave time) | Calendar/DatePicker, TimeField, ColorPicker |
| editor | editor (new) | Text/code editor, syntax highlighting (D12) |
## 7. Coding standards — AGENTS.md adaptation Deferred until a real need: Markdown/TextView, Dock, VirtualList, window-wide text selection, a motion system (use gpui's built-in animations first), charts.
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): ## 7. Milestones
From reya/signed (kept nearly verbatim): | Phase | Scope | Acceptance |
| --- | --- | --- |
| A — scaffold (next) | Workspace per §4.1: theme stub, icons crate (`IconRole` + Lucide fallback), core crate with feature scaffolding (zero components), facade, gallery shell, CI incl. feature checks | `gallery` opens a window reading the theme global; `core` builds at default / no-default / all / each-feature-alone |
| B — theme engine core | Token model + resolve + presets (neutral fallback, tokyo-night) + macOS source + `ThemeWatcher`; gallery theme inspector | Button + Checkbox painted from live macOS system tokens; flipping system appearance repaints live |
| C — controls | Wave 1 in `core`, one feature each; macOS SF Symbols resolver behind the icon seam | Each control: tests + gallery page + native-reference screenshots; icon browser shows per-role resolution |
| D — input | Wave 2 | Single-line input with cursor, selection, clipboard; NumberInput |
| E — overlays | Wave 3 | Focus trapping and escape/dismiss semantics per overlay |
| F — platforms + data | `crates/data` + Windows/GNOME/KDE/Omarchy theme sources + Segoe Fluent/freedesktop icon resolvers | Side-by-side gallery screenshots per platform |
| G — pickers + polish | Wave 5 → 0.1; start `crates/editor` if wanted | — |
- Correctness and clarity first; speed secondary unless specified. Gate for every phase: `cargo fmt --check`, `cargo clippy --workspace --all-targets -- -D warnings`, `cargo test`, gallery smoke run.
- 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): ## 8. Testing
- Never invent APIs — search the ported source for real signatures before writing code. Pure tests for resolve/overlay chain, payload parsing (fixtures), icon resolution tables, and metrics; context tests for the theme global lifecycle; headless UI tests for activation, controlled round-trips, focus, disabled; gallery smoke run. GPUI executor timers in tests. Only meaningful tests — state semantics, a11y mapping, invariants (AGENTS.md Tests rule).
- 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 ## 9. Licensing
1. **Pure tests** — theme resolve overlay chain, projection mapping (presets × modes), reader payload parsing (fixtures), metrics derivations, geometry math (popup clamping, scrollbar ranges). - All code in this repo is original: Apache-2.0 (D6).
2. **Context tests** — theme global lifecycle, appearance-change handling, token re-install. - gpui: Apache-2.0 dependency, nothing copied.
3. **UI integration tests** — headless windows: pointer/keyboard activation, controlled value round-trips, focus movement and traps, disabled/readonly, event order. - Lucide icons: ISC — bundled subset with attribution in NOTICE.
4. **Example smoke** — gallery launches, switches theme/preset/mode without panic. - SF Symbols and Segoe Fluent Icons: used through the operating system at runtime only; never extracted or redistributed.
- native-theme, gpui-omarchy, gpuikit, bezel: inspiration only; no code copied. If that ever changes, respect their licenses.
Conventions: GPUI executor timers in tests; regression test first for bugs; screenshots only for facts the semantic tree cannot express. ## 10. Risks
## 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 | | 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 | | zed `main` churns gpui APIs | Pinned rev; bumps are deliberate (re-verify, fix, note in commit message); 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 | | Writing our own input/editor is the biggest lift | Single-line first; no LSP/highlighting/composition until needed; selection stays private to the field |
| 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 | | Per-component features multiply configurations | Features gate whole modules, additive only; CI checks default / none / all / each-alone, not the full matrix |
| Platform icon lookups vary (missing fonts, themes) | Resolver cascade always ends in the bundled Lucide fallback; missing glyph logs once and falls back |
| Per-OS readers are a maintenance burden | Thin readers over parseable payloads + fixture tests; graceful fallback chain (system → preset → fallback theme) |
| "Native feel" is judgment | Named native reference per component; per-platform screenshot review each phase |
| Omarchy `colors.toml` key set evolves | Parse tolerantly (unknown keys ignored, missing keys fall back); track the theme-set script 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 | | 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 ## 11. Decisions
| # | Decision | Status / recommendation | | # | Decision |
| --- | --- | --- | | --- | --- |
| D1 | Theme source | **Decided (your feedback):** custom engine inspired by native-theme; no `native-theme` dependency | | D1 | Custom theme 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 from zed's repository only, pinned rev; no `gpui-pre`, no forks, no `gpui-unofficial` |
| D3 | GPUI source | **Decided (your feedback):** upstream zed git only, no `gpui-pre` — pinned rev (recommended) | | D4 | Desktop only; no WASM |
| D4 | WASM target | Not initially; desktop only | | D6 | Apache-2.0 for this repo's code |
| D5 | Separate `base` + `kit` crates vs one crate | Separate (compiler-enforced behavior/presentation boundary) | | D7 | `gpui-native-kit` stays the thin facade package name; it only re-exports crates behind features. Applications may depend on `gpui-native-core` directly |
| D6 | License for our code | Apache-2.0 | | D8 | macOS first, then Windows 11 (WinUI), then GNOME → KDE → Omarchy; fallback theme from day one |
| D7 | Facade keeps package name `gpui-native-kit` | Yes | | D11 | Bundled presets: neutral `fallback`, `tokyo-night`, per-platform presets for testing readers; Omarchy files load as presets too |
| 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 | | D12 | Editor deferred; `crates/editor` is created when wave 2's multiline editing or highlighting demands it |
| 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) | | D13 | Accent shades hybrid: stated platform ramps where available (WinUI, KDE), OKLCH derivation elsewhere |
| D10 | Icon sets (Material/Lucide bundling) | Out of scope initially; revisit after Phase 5 | | D14 | Custom-theme live reload via `notify` |
| 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 | | D15 | No porting, no UI-toolkit dependencies; inspiration only, always simplified — never mirrored 1:1 |
| D12 | Editor primitive | Deferred; schedule after Phase 5 if wanted | | D16 | No separate behavior crate; behavior lives in each component; shared helpers promoted at the third use |
| D13 | Accent state shades | Hybrid: stated platform ramps where available (WinUI, KDE), OKLCH derivation elsewhere | | D17 | `UPSTREAM.md` removed; provenance = §3 pins + git history |
| D14 | Custom-theme live reload | Yes, via `notify`; the dep is justified by the custom-theme requirement | | D18 | Crate split for consumption: `core` (every-app components) → `editor`, `data`, … (advanced) → thin `gpui-native-kit` facade; one cargo feature per component, additive, module-gated |
| D19 | System icons via semantic `IconRole`s with per-platform resolvers (SF Symbols / Segoe Fluent Icons / freedesktop) and a bundled Lucide fallback; platform glyph sets are used at runtime only, never bundled |
Dropped from rev 3: D2, D5, D9 (port-era decisions). D10 (no icon bundling) is superseded by D19.
## 12. Immediate next steps ## 12. Immediate next steps
1. Confirm the remaining recommendations (D4–D12) or adjust. 1. Phase A scaffold (§4.1, §7): workspace, toolchain pin, CI, theme stub, icons crate with `IconRole` + Lucide fallback, core feature scaffolding, facade, gallery shell.
2. Phase 0: workspace restructure, zed pin, `AGENTS.md`, `UPSTREAM.md` skeleton, CI, window smoke test. 2. Phase B theme engine core, macOS adapter first.
3. Port unit 0 (foundation seed) → your review. 3. Then wave 1 controls.
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.