Files
gpui-native-kit/docs/PLAN.md
T
2026-10-28 19:17:35 +07:00

286 lines
28 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# gpui-native-kit — Project Plan
Rev 6 (2026-10-28). Changes from rev 5: components are built **one element at a time behind a review gate** — each unit is designed, implemented, tested, shown in the gallery, and reviewed before the next starts (batch waves are gone). [gpui-kit `base`](https://github.com/longbridge/gpui-kit/tree/main/crates/base) is re-admitted as the **API-design reference**: its unstyled-control conventions (controlled state, one activation path for pointer + Enter + Space, semantic states, a11y labels) shape our component APIs, and small utilities may be adapted where they help. Everything else (empty-project reset, theme engine as the product, crate split, per-component features) stands.
## 1. Vision
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.
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`.
## 2. Evaluated foundations (2026-10-28)
| Crate | What it is | Verdict | What we take |
| --- | --- | --- | --- |
| gpui-kit (longbridge) | Unstyled `base` behavior layer (controlled state, one activation path, semantic states, a11y roles, focus utilities) + styled component library | Not a wholesale foundation: the full base is larger than we need and pins `gpui-pre` snapshots. **But `base` is the API-design reference for our components** (D20), and its small utilities are adapted where they help | API shapes: `Button::new("save")` unstyled + `.on_click` as the single path for pointer/Enter/Space; controlled `checked` + `on_change(next)`; `disabled`/`selected` semantic states resolved after the builder chain, disabled last; `accessibility_label`; composable parts (`CheckboxIndicator`, `SwitchTrack`/`SwitchThumb`) where a component needs them. Utilities worth adapting: `h_flex`/`v_flex`, `StyledExt`, `StateStyle`, focus-ring helper |
| 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 |
Decision D15: we depend on no UI toolkit and port no wholesale subsystems. These crates (plus native-theme, D1) are read for API and design ideas — 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:
```toml
gpui = { git = "https://github.com/zed-industries/zed", rev = "ec86047d67acd6fddd81392f44ed6ebcc6e7f531" }
gpui_platform = { git = "https://github.com/zed-industries/zed", rev = "ec86047d67acd6fddd81392f44ed6ebcc6e7f531", features = ["font-kit"] }
```
`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.
- **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.
- **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.
- **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.
- **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.
- **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.
## 4. Architecture
### 4.1 Workspace layout (Phase A)
```text
gpui-native-kit/ # repo root = workspace root
├── Cargo.toml # [workspace]; root package = facade
├── rust-toolchain.toml
├── AGENTS.md
├── docs/PLAN.md
├── src/lib.rs # gpui-native-kit: thin facade (re-exports, feature-driven)
├── crates/
│ ├── theme/ # gpui-native-theme: the native theme engine
│ ├── icons/ # gpui-native-icons: system icons + bundled fallback
│ ├── core/ # gpui-native-core: components every app needs
│ │ # (one cargo feature per component)
│ └── editor/ # gpui-native-editor: created when wave 2 needs it (D12)
└── examples/
└── gallery/ # showcase; a page per component + theme/icon inspector
```
Advanced crates (`data` for Table/Tree/List, `charts`, `pickers`) are created on demand when their wave starts — not speculative scaffolding.
```mermaid
graph TD
FAC[gpui-native-kit facade — optional] --> CORE[gpui-native-core]
FAC --> FUT[editor / data / …]
CORE --> T[gpui-native-theme]
CORE --> I[gpui-native-icons]
T --> P[gpui + gpui_platform — zed, pinned rev]
I --> P
```
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.
### 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
- **One element at a time, review-gated (D20).** Each component is one unit: API sketch → implementation → tests → gallery page → review. The next unit starts only after the previous one is approved. No batch waves.
- **API shape follows gpui-kit `base` conventions** (adapted, simplified where we can):
- Controlled components: the owner passes the current value, the callback reports the **next** value, the owner updates state and calls `cx.notify()`. `Checkbox::new(id).checked(v).on_change(move |next, window, cx| …)`.
- One activation path: pointer, Enter, and Space all fire the same `on_click` (gpui synthesizes keyboard clicks for tracked focus elements).
- Semantic states as builders — `disabled(bool)`, `selected(bool)`, `checked(bool)`, `pressed(bool)` — resolved **after** the builder chain and painted from theme state colors; disabled resolves last and always wins.
- `accessibility_label(...)` on every control; correct accesskit role per control (`Button`, `CheckBox`, `Switch`, `RadioButton`, `Link`, …).
- Composable parts (`CheckboxIndicator`, `SwitchTrack`/`Thumb`) only where a component genuinely needs independently painted pieces — we paint from tokens, so most parts collapse into the component itself; decide per review.
- **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.
- **Deliberate divergence from base:** our `Link` opens `href` through the platform by default (`cx.open_url`) — native feel is the product — with `on_click` as an override for apps that route internally. Base never opens URLs; we do unless told otherwise.
- 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
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 --> TK[token projection onto the Theme global]
TK --> WIN[all windows repaint]
EVT[runtime change events] -->|re-read, re-resolve, re-install| TK
```
1. **Read** — one adapter per platform produces a normalized `SystemTheme` (mode, accent, semantic palette, accessibility flags, style hint).
2. **Detect** — capability cascade: Omarchy marker → KDE (`kdeglobals` present) → portal reachable (GNOME/generic Linux) → fallback. macOS/Windows by compile target.
3. **Resolve** — overlay chain: platform reader → bundled preset → user theme → runtime overrides → accessibility adjustments. Pure function, unit-testable without a GUI.
4. **Project** — `ResolvedTheme` → the token structs components consume, installed on the theme `Global`.
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.
## 5. Theme engine (`crates/theme`)
### 5.1 Architecture
- **`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.
- **`SystemTheme`** — normalized snapshot: mode, accent, semantic palette, high-contrast, reduce-motion, reduce-transparency, text-scaling, style hint, platform identity.
- **`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.
### 5.2 Role mapping
| Token role | Source |
| --- | --- |
| `background` / `foreground` | window background / default text |
| `surface` / `surface_foreground` | cards / secondary areas |
| `primary`, `secondary`, `accent` | button + selection colors (macOS: accent-derived) |
| `muted` | secondary text and hover shades |
| `destructive` | danger role |
| `border` / `input` | control border line / field background |
| `ring` | focus ring |
| `selection` | text selection background |
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.
The set stays role-minimal and grows one concept at a time, driven by the unit that needs it. Known planned additions:
- **Overlay scrim + shadow/elevation tokens** — with the overlay units (Popover/Dialog/Menu, unit 11+); §5.5's popup elevation promise. No current role covers the modality backdrop.
- **`success` / `warning` roles** — with the first severity-bearing component (Toast, badges, validation); the Omarchy mapping above already anticipates them.
- **`knob` / control-fill role** — only if Slider (unit 7) confirms the Switch's borrowed roles (`primary_foreground` thumb, `border` off-track) as a second consumer; until then no expansion.
Controls through unit 10 paint from existing roles and ramps.
### 5.3 Platform 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 |
### 5.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.
### 5.5 Native metrics
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).
### 5.6 Custom themes
- `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.
### 5.7 Implementation vehicles
`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.
### 5.8 What we do differently from native-theme
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. Component roadmap (review units, in order)
Each row is one review unit: build it, show it in the gallery, review, then move on.
| Unit | Crate | Component | Notes |
| --- | --- | --- | --- |
| 1 | core | Button | Variants (primary/secondary/ghost/destructive) painted from token ramps; single activation path; disabled + a11y role. The API template for everything after. |
| 2 | core | Checkbox | Controlled `checked` + `on_change(next)`; Check icon from the icons crate. |
| 3 | core | Switch | Controlled pill + thumb; thumb slides once motion exists. |
| 4 | core | Radio + RadioGroup | Group owns the selected value; radios never self-uncheck. |
| 5 | core | Toggle | Controlled `pressed` (aria-pressed). |
| 6 | core | Link | `href` opens via platform by default; `on_click` overrides. |
| 7 | core | Slider | Click-to-set + in-track drag; controlled value. |
| 8 | core | Progress | Determinate + indeterminate. |
| 9 | core | Avatar | Initials; image slot later. |
| 10 | core | Input (single-line) then multiline, NumberInput | String buffer + `unicode-segmentation` word movement; the field owns its own selection. May seed `crates/editor`. |
| 11+ | core | Popover, Tooltip, Menu, Dialog, AlertDialog, Sheet, Toast | One unit each, same gate. |
| then | data (new) | List, Table, Tree, Tabs, Sidebar | Created on demand, same gate. |
| then | pickers or data | Calendar/DatePicker, TimeField, ColorPicker | Decide at unit time. |
| editor | editor (new) | Text/code editor, syntax highlighting (D12) | Same gate. |
Deferred until a real need: Markdown/TextView, Dock, VirtualList, window-wide text selection, a motion system (use gpui's built-in animations first), charts.
## 7. Milestones
| 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. Done: `resolve` overlay chain (base → system → preset → mode override → high-contrast) is pure and tested; OKLCH accent-ramp engine; `NSColor`/`NSWorkspace` adapter; distributed-notification watchers; inspector with live mode/preset switching. (The "Button + Checkbox painted from live tokens" proof moves to Phase C — those components land there; the gallery inspector proves the live pipeline meanwhile.) |
| C — controls | One review unit at a time (§6, units 1–9), each: API per §4.3 + tests + gallery page → review gate. macOS SF Symbols resolver behind the icon seam lands with the first unit that shows it matters | Per unit: activation, controlled round-trip, and disabled inertness tested; gallery page shows the live control against the theme; your approval closes the unit |
| 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 | — |
Gate for every phase: `cargo fmt --check`, `cargo clippy --workspace --all-targets -- -D warnings`, `cargo test`, gallery smoke run.
## 8. Testing
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).
## 9. Licensing
- All code in this repo is original: Apache-2.0 (D6).
- gpui: Apache-2.0 dependency, nothing copied.
- gpui-kit `base`: API-design reference (D20); where a small utility is adapted rather than reinvented, keep Apache-2.0 compliance — note the origin in a file-header comment or NOTICE.
- Lucide icons: ISC — bundled subset with attribution in NOTICE.
- 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.
## 10. Risks
| Risk | Mitigation |
| --- | --- |
| zed `main` churns gpui APIs | Pinned rev; bumps are deliberate (re-verify, fix, note in commit message); CI catches drift early |
| Writing our own input/editor is the biggest lift | Single-line first; no LSP/highlighting/composition until needed; selection stays private to the field |
| 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 |
## 11. Decisions
| # | Decision |
| --- | --- |
| D1 | Custom theme engine inspired by native-theme; no `native-theme` dependency |
| D3 | gpui from zed's repository only, pinned rev; no `gpui-pre`, no forks, no `gpui-unofficial` |
| D4 | Desktop only; no WASM |
| D6 | Apache-2.0 for this repo's code |
| 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 |
| D8 | macOS first, then Windows 11 (WinUI), then GNOME → KDE → Omarchy; fallback theme from day one |
| D11 | Bundled presets: neutral `fallback`, `tokyo-night`, per-platform presets for testing readers; Omarchy files load as presets too |
| D12 | Editor deferred; `crates/editor` is created when wave 2's multiline editing or highlighting demands it |
| D13 | Accent shades hybrid: stated platform ramps where available (WinUI, KDE), OKLCH derivation elsewhere |
| D14 | Custom-theme live reload via `notify` |
| D15 | No wholesale porting, no UI-toolkit dependencies. Component APIs follow gpui-kit `base` conventions where they fit (D20), always simplified — never mirrored 1:1; small utilities may be adapted with attribution |
| D16 | No separate behavior crate; behavior lives in each component; shared helpers promoted at the third use |
| D17 | `UPSTREAM.md` removed; provenance = §3 pins + git history |
| 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 |
| D20 | Components land one element at a time behind a review gate: API sketch → implementation → tests → gallery page → review → next. gpui-kit `base` is the API-design reference for these units; its conventions (controlled state, one activation path, semantic states, a11y labels) and selected utilities are adopted, simplified |
Dropped from rev 3: D2, D5, D9 (port-era decisions); the review gate returns in a lighter form as D20. D10 (no icon bundling) is superseded by D19.
## 12. Immediate next steps
1. ~~Phase A scaffold~~ ✅ — workspace, toolchain pin, CI, theme seed, icons crate, core feature scaffolding, facade, gallery shell.
2. ~~Phase B theme engine core~~ ✅ — resolve pipeline, OKLCH ramps, presets, macOS source + watchers, gallery theme inspector.
3. **Unit 1: Button** (next) — API per §4.3 modeled on gpui-kit `base`'s Button (single activation path, `disabled`, a11y role + label), painted from theme ramps; tests for activation / disabled inertness / keyboard activation; gallery page. → your review, then unit 2 (Checkbox).