Files
gpui-native-kit/docs/PLAN.md
T
2026-10-27 16:33:16 +07:00

34 KiB
Raw Blame History

gpui-native-kit — Project Plan

Status: rev 3 · 2026-10-27

Changes in rev 3 (theme engine upgrade)

  • Per-platform adapters read real semantic system colors (macOS NSColor, Windows WinRT UISettings, Linux portal/KDE/GNOME, Omarchy theme files) instead of approximating platform palettes by hand.
  • Accent-ramp engine: one accent color in → hover/pressed/selected/subtle shades out (stated platform ramps where available, OKLCH derivation elsewhere).
  • Native metrics promoted to a first-class deliverable (control heights, radii, fonts, focus rings, scrollbars) — color parity alone does not make native feel.
  • Platform targets fixed: macOS, Windows 11 (WinUI), GNOME, KDE, Omarchy, plus a neutral fallback theme for unknown systems.
  • Custom themes: sparse TOML definitions with overlay merge priority, config directory + CLI flag, live reload, single-accent override.

Changes from rev 1

  • Component-by-component porting. No wholesale vendoring of gpui-kit base. Components are copied one at a time, each behind a review gate; the next port starts only after the previous is approved.
  • Custom native theme engine. No dependency on the native-theme crate. We build our own engine inspired by its design (sparse theme definition → resolved theme → toolkit projection, presets, accessibility preferences) with our own per-platform readers.
  • Upstream GPUI only. The only GPUI is gpui / gpui_platform from zed-industries/zed (git, pinned revision). No gpui-pre, no gpui-component, no gpui-kit facade dependency.

1. Vision

gpui-native-kit is a Rust component library for GPUI whose components look native to the user's operating system.

Source Role in this project
GPUI Kit base 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 The UI framework, from Zed's repository at a pinned revision.
native-theme Design inspiration only. Its layered model (sparse Theme → ResolvedTheme → connector, presets, accessibility preferences, per-platform readers) is the blueprint for our own engine. We depend on none of its code.

The core idea: base primitives own behavior but no visuals. We add the missing layers — a theme engine that reflects the user's system, a projection from resolved theme to base's semantic tokens and native control metrics, and styled components that paint the ported primitives with them.

What "native" means here. Components are rendered by GPUI to approximate the platform toolkit's appearance (colors, control heights, radii, focus rings, fonts, light/dark, accent color) — they are not OS-native controls.

2. Background facts (verified)

  • GPUI install shape — the only framework dependency:
    gpui           = { git = "https://github.com/zed-industries/zed", rev = "<pin>" }
    gpui_platform  = { git = "https://github.com/zed-industries/zed", rev = "<pin>", features = ["font-kit"] }
    
    (font-kit required on macOS for glyph rasterization; Linux needs wayland and/or x11; Windows needs nothing.) No gpui-pre: that package is gpui-component's republishing mechanism for Zed snapshots and plays no role here. Consequence we must manage: source ported from upstream gpui-base was written against a particular GPUI snapshot and may differ slightly from our pinned main revision — each port unit applies minimal adaptation patches and records them.
  • gpui-base facts (from the official docs): init must run once before opening windows; open_window-equivalent plumbing wraps the root view in a Root (overlay hosting, window state, rem size). Theme tokens: ColorTokens::light()/dark() provide semantic palettes — background, foreground, surface, surface_foreground, primary, secondary, muted, accent, destructive, border, input, ring, selection + foreground roles — installed as SemanticThemeTokens (which also carries radius, spacing, typography, shadow scales) on a theme Global. Base paints its own chrome (scrollbars, resize handles) from these tokens.
  • Ported-component catalog (37 components): Accordion, Alert Dialog, Avatar, Button, Calendar, Checkbox, Collapsible, Color Picker, Combobox, Date Picker, Dialog, Editor, Hover Card, Input, Link, Nav Stack, Number Input, OTP Input, Pagination, Popover, Popup, Progress, Radio, Radio Group, Resizable, Scrollbar, Select, Sheet, Slider, Switch, Table, Tabs, Textarea, Time Field, Toast, Toggle, Toggle Group, Tooltip. Plus infrastructure systems ported lazily when first needed: Motion, Virtual List, Dock, History, Plot, TextView, Text Selection. Upstream examples live in crates/base/examples and are the per-component reference behavior we bring along.
  • native-theme design (the blueprint we adapt): SystemTheme::from_system() reads macOS / Windows / KDE / GNOME; a sparse, TOML-shaped Theme (Option fields) resolves into a fully populated ResolvedTheme; bundled presets; AccessibilityPreferences (text scaling, reduce motion, reduce transparency); a thin per-toolkit "connector" maps the resolved theme onto the framework's theming system. Its existing GPUI connector targets gpui-component (139 ThemeColor fields, geometry builders, theme-drawn widgets) — useful as a case study, not a dependency.
  • xdg-desktop-portal Settings (verified against the spec): org.freedesktop.appearance standardizes color-scheme (u), accent-color ((ddd) RGB 0–1), contrast (u), and reduced-motion (u), with a SettingChanged signal — one DBus interface covers mode, accent, contrast, and reduce-motion across GNOME, KDE, and generic Linux.
  • Omarchy theme contract (verified against the manual and Omarchy's shipped tokyo-night theme): the active theme is $HOME/.local/state/omarchy/current/theme/colors.toml with the theme name in theme.name beside it (legacy location ~/.config/omarchy/current; current/theme is a symlink into ~/.config/omarchy/themes or /usr/share/omarchy/themes). colors.toml states mode = "dark" | "light" plus semantic keys — accent, selection, muted, background/dark_background/darker_background/lighter_background, foreground/dark_foreground/light_foreground/bright_foreground, and red/yellow/orange/green/cyan/blue/magenta/brown with bright_* variants; the ANSI color0..15 format is an accepted alternative.
  • Prior art — gpui-omarchy (MIT): a gpui-kit component library styled exclusively for Omarchy. It validates this project's shape (base + system-style layer) and its theme handling is the reference for our Omarchy adapter: read colors.toml + theme.name, watch filesystem events for palette edits and theme-symlink replacement, follow_system(cx) / explicit apply to pause/resume following, whole-palette fallback to Tokyo Night when files are missing or invalid. We differ deliberately: upstream zed GPUI only (it uses the published gpui-kit facade), per-component porting, and five other platform targets.

3. Architecture

3.1 Workspace layout

The repo currently holds a single empty package (gpui-native-kit/, no dependencies). Phase 0 restructures the repo root into a cargo workspace:

gpui-native-kit/                 # repo root = workspace root
├── Cargo.toml                   # [workspace] members = ["crates/*"]
├── rust-toolchain.toml          # latest stable (gpui requires recent stable)
├── AGENTS.md                    # adapted coding guidelines (§7)
├── UPSTREAM.md                  # zed rev pin, per-port-unit provenance & divergence log
├── docs/PLAN.md
├── crates/
│   ├── base/                    # gpui-native-base: foundation seed + ported components
│   │                            # (grows one component per approved review unit)
│   ├── theme/                   # gpui-native-theme: custom native theme engine + projection
│   └── kit/                     # gpui-native-kit: styled native components (facade)
│                                # (grows alongside each base port unit)
└── examples/
    └── gallery/                 # showcase application; gains a page per port unit
  • The existing empty package becomes crates/kit (keeps the gpui-native-kit package name).
  • base and kit are separate crates so the "no presentation in base, no behavior in kit" boundary is compiler-enforced. (Decision point D5 covers keeping them separate vs one crate.)

3.2 Dependency direction

examples/gallery ──▶ gpui-native-kit ──▶ gpui-native-theme ──▶ gpui-native-base ──▶ gpui, gpui_platform (zed, pinned rev)
                            │                     │
                            └─────────────────────┴──▶ gpui
  • base depends only on GPUI. theme depends on GPUI + base (it writes base's token global). kit depends on all three. One-way, no cycles.
  • Nothing depends on gpui-component, gpui-kit, or native-theme.

3.3 Theme pipeline (custom engine, native-theme-inspired)

graph TD
    OS[Adapters: macOS / Windows 11 / GNOME / KDE / Omarchy / fallback] --> ST[SystemTheme normalized snapshot]
    PR[ThemeDefinition presets + user overlays] --> RS[resolve]
    ST --> RS
    RS --> RT[ResolvedTheme + AccessibilityPreferences]
    RT --> BR[projection: bridge + metrics]
    BR --> TK[SemanticThemeTokens onto base Theme global]
    BR --> GM[per-widget native metrics for kit components]
    TK --> WIN[windows]
    GM --> WIN
    EVT[runtime change: gpui appearance events, portal signals, pref changes] -->|re-read, re-resolve, re-install, repaint| BR
  1. Read — one adapter per target platform produces a normalized SystemTheme (mode, accent, semantic palette, accessibility flags, style hint):
    • macOS: real NSColor semantic colors (windowBackgroundColor, labelColor, secondaryLabelColor, controlColor, controlBackgroundColor, separatorColor, controlAccentColor, selectedTextBackgroundColor, keyboardFocusIndicatorColor, …) read in the current effective appearance via objc2-app-kit; contrast/reduce-motion/reduce-transparency from NSWorkspace flags; changes via NSDistributedNotificationCenter + GPUI appearance events.
    • Windows 11 (WinUI): documented WinRT UISettings.GetColorValue (AccentColor, AccentLight1–3, AccentDark1–3, Background, Foreground, …) via the windows crate, plus TextScaleFactor and AccessibilitySettings high-contrast; Personalize registry as fallback; runtime via UISettings.ColorValuesChanged.
    • GNOME / generic Linux: xdg-desktop-portal org.freedesktop.appearance — standardized color-scheme, accent-color (RGB 0–1), contrast, reduced-motion with SettingChanged (verified against the portal spec) — via zbus; gsettings org.gnome.desktop.interface as fallback.
    • KDE: kdeglobals ini ([Colors:Window/Button/Selection/…], scheme hash, accent) parsed with fixture tests; portal keys still honored for mode/accent.
    • Omarchy: read $HOME/.local/state/omarchy/current/theme/colors.toml + theme.name (legacy ~/.config/omarchy/current), accepting both the semantic key set and ANSI color0..15; watch the current dir for palette edits and theme-symlink replacement; invalid or missing palette falls back to Tokyo Night (Omarchy's default); portal keys supply contrast/reduced-motion.
    • Fallback / unknown system: no adapter applies, or it fails → bundled neutral light/dark theme; also the per-field fallback for values a reader could not supply.
  2. Detect — capability cascade picks the adapter: Omarchy marker (~/.local/state/omarchy or ~/.config/omarchy) → KDE (kdeglobals present) → portal reachable (GNOME/generic) → fallback. macOS/Windows by compile target.
  3. Resolve — overlay chain: platform defaults → preset (ThemeDefinition) → user overlay → accessibility adjustments. Output: ResolvedTheme (role palette + control metrics) and AccessibilityPreferences.
  4. Project — ResolvedTheme → base's SemanticThemeTokens (installed on the theme Global, covering base-drawn chrome) + native metrics consumed by kit components.
  5. Observe — a ThemeWatcher per adapter forwards real change events (distributed notifications, WinRT ColorValuesChanged, portal SettingChanged, inotify on KDE/Omarchy files) into the app; GPUI appearance events remain the light/dark baseline everywhere. Each event triggers re-read → re-resolve → re-install → repaint all windows. We are the only writer of base's theme global, which keeps re-application simple.

3.4 Component design rule

Each kit component wraps a ported base primitive and owns only presentation:

  • The base primitive provides activation, focus, keyboard, dragging, controlled state, AccessKit role.
  • The kit component paints track/thumb/indicator/panel etc. from ResolvedTheme leaves, with geometry from native metrics.
  • No behavior is reimplemented; no color/radius literal appears outside theme's projection and metrics modules.

4. Porting process & component order

4.1 The review gate

Work proceeds in port units. One unit = one component (or one infrastructure system, or the foundation seed). The next unit starts only after you approve the previous one.

Each unit delivers:

  • the ported module(s) in crates/base (behavior, unstyled), copied from upstream longbridge/gpui-kit crates/base at a recorded upstream commit;
  • the styled counterpart in crates/kit (from Phase 3 onward — themed at birth; see D9);
  • its example wired into the gallery (ported from upstream's example for that primitive, adapted);
  • tests (ported and/or new);
  • a UPSTREAM.md entry: upstream commit, files touched, every adaptation patch made to compile against our zed pin, and any intentional divergence.

Unit checklist before requesting review:

  • compiles against the pinned zed rev; cargo clippy clean
  • zero visual literals in base; all styling reads theme/metrics (kit side)
  • stable domain-derived ElementIds; controlled state round-trips through the owner
  • pointer + keyboard + disabled behavior verified by test
  • gallery page renders in light and dark
  • UPSTREAM.md provenance recorded

4.2 Port order (dependency-aware)

Unit 0 — foundation seed (prerequisite, not a "component"): init, Root/window plumbing, theme global with ColorTokens/SemanticThemeTokens, prelude, small shared utils, gallery shell.

Then, in order — leaf controls first, popup infrastructure before anchored controls, overlays together, data and pickers last:

# Unit Notes
1 Button First full unit; proves the whole pipeline
2 Checkbox
3 Switch
4 Radio + Radio Group One unit
5 Toggle + Toggle Group One unit
6 Link
7 Slider First drag interaction
8 Progress
9 Avatar
10 Input Brings input state machinery
11 Number Input Reuses Input
12 Textarea
13 OTP Input
14 Popup Low-level anchored-content host; needed by 15–19
15 Popover
16 Select
17 Combobox Input + popup
18 Tooltip
19 Hover Card
20 Dialog Overlay focus trap/restore
21 Alert Dialog
22 Sheet
23 Toast May pull in Motion as a lazy infra unit
24 Tabs
25 Accordion
26 Collapsible
27 Pagination
28 Scrollbar
29 Resizable
30 Table May pull Virtual List
31 Tree May pull Virtual List
32 Calendar
33 Date Picker Input + popup + Calendar
34 Time Field
35 Color Picker
36 Nav Stack May pull History

Infrastructure systems (Motion, Virtual List, Dock, History, Plot, TextView, Text Selection) are ported lazily — each as its own review unit — when the first component that needs it comes up (suggestions in the table). Editor is deliberately out of the initial 36; schedule it after Phase 5 if wanted (D12).

5. Milestones

Phase 0 — Workspace scaffold

  • Restructure repo root into a cargo workspace (§3.1); move the existing package to crates/kit.
  • Dependencies: gpui + gpui_platform from zed at a pinned rev; record the rev in UPSTREAM.md.
  • Toolchain file, rustfmt, clippy (deny warnings in CI), license check.
  • Write AGENTS.md (§7) and the UPSTREAM.md skeleton.
  • Minimal main.rs: open one window with a div.
  • Done when: cargo run -p gpui-native-kit shows a window on macOS; CI skeleton (build + clippy + fmt, macOS/Windows/Linux matrix) passes.

Phase 1 — Port unit 0: foundation seed

  • Port init/Root/theme-global/tokens/prelude into crates/base; gallery shell in examples/gallery.
  • Default ColorTokens palettes only; no system reading yet.
  • Done when: gallery runs; tokens switchable between light/dark manually; you approve unit 0.

Phase 2 — Custom native theme engine (macOS first)

  • crates/theme: ThemeSource trait + SystemTheme / ThemeDefinition / ResolvedTheme / AccessibilityPreferences model; macOS adapter (real NSColor semantic reads + accessibility flags); accent-ramp engine (OKLCH); resolve overlay chain; projection to base tokens + the macOS metrics table; install + watcher (distributed notifications + GPUI appearance events); neutral fallback theme; CLI override flag for theme/preset/mode.
  • Reader fixture tests (parse canned payloads); resolve/projection unit tests for presets × modes.
  • Done when: the gallery reflects the live macOS theme; toggling system appearance switches at runtime; a preset can be forced via CLI flag; you approve the engine.

Phase 3 — Port units 1–13 (controls + text)

  • Units 1–13 per §4.2, each styled from ResolvedTheme at birth, with metrics (control heights, paddings, radii, focus ring) from the engine.
  • Per-component UI integration tests; gallery pages.
  • Done when: gallery shows these controls native-styled in light/dark; all units approved.

Phase 4 — Port units 14–27 + platform expansion

  • Popup/overlay/navigation units (14–27), including overlay focus-trap/restore and stacked dismissal verification.
  • Parallel track: Windows adapter (WinRT UISettings + ColorValuesChanged); Linux adapters — portal via zbus (baseline), KDE kdeglobals, GNOME gsettings, Omarchy theme-dir reader (schema verified on a real install); per-platform metrics tables; runtime-change observation on each platform; side-by-side screenshot verification against each toolkit.
  • Per-platform CI screenshots for the gallery (best-effort visual regression).
  • Done when: gallery runs native-themed on macOS, Windows 11, GNOME, KDE, and Omarchy, and falls back cleanly on unknown systems; units approved.

Phase 5 — Port units 28–36, lazy infra, polish

  • Data + picker units (28–36); lazy infrastructure units as surfaced (Virtual List, Motion, History).
  • Showcase polish (live theme switching UI), README, per-component doc comments with compilable examples, CHANGELOG, test sweep.
  • Decide on publishing (crates.io vs git tag).
  • Done when: all units approved; a fresh contributor can clone, build, run the gallery, and find the docs they need.

6. Theme engine design (custom, crates/theme)

6.1 Architecture

  • ThemeSource trait — one adapter per target platform: snapshot() -> SystemTheme plus watch(callback) for live changes. Adapters are thin over parseable payloads (registry dumps, ini text, portal replies, theme JSON/CSS) so parsing is fixture-testable on any OS; platform calls live in a small seam.
  • SystemTheme — normalized snapshot: mode (light/dark), accent color, semantic palette (the roles the adapter can supply), high-contrast flag, reduce-motion, reduce-transparency, text-scaling factor, style hint (e.g. "Breeze"), platform identity.
  • Detection cascade — Omarchy marker (~/.local/state/omarchy or ~/.config/omarchy) → KDE (kdeglobals present) → portal reachable (GNOME/generic Linux) → fallback theme. macOS/Windows chosen by compile target.
  • ThemeDefinition — sparse, TOML-serializable struct of Option fields; used for bundled presets and user themes. May omit almost anything.
  • ResolvedTheme — fully populated: every color role has a value; metrics have platform defaults where the definition is silent. resolve(system, definitions…, prefs) is a pure function — unit-testable without a GUI.
  • AccessibilityPreferences — text-scaling factor (≥ 1, clamped finite), reduce-motion, reduce-transparency, high-contrast. Forwarded: text scaling multiplies font sizes (and, through rem, all rem-relative sizing); reduce-motion forwarded to GPUI; reduce-transparency removes overlay backdrops; high-contrast sharpens borders and removes subtle fills.
  • Projection — ResolvedTheme → base SemanticThemeTokens + metrics structs consumed by kit components.
  • ThemeWatcher — owns adapter watches, forwards change events to an app entity, drives re-resolve → re-project → repaint (pipeline step 5).

6.2 Initial role mapping (refined during Phase 2)

Base role (ColorTokens) ResolvedTheme source
background / foreground window background / default text
surface / surface_foreground card / secondary areas
primary, secondary, accent button + selection colors (macOS: accent-derived)
muted muted/secondary text and hover shades
destructive danger/error role
border button/input border line
input text field background/border
ring focus ring color
selection text selection background (macOS: selected-text color)

Rules: every mapping lives in one module; derivations from neighboring roles are documented where the definition is silent; never literals. Tests assert finite, distinct, semantically sane tokens for every preset × mode.

Omarchy colors.toml maps almost 1:1 onto our roles: background → background, lighter_background → surface, dark_background/darker_background → elevated/overlay depth, foreground → foreground, dark_foreground → muted foreground, selection → selection, accent → primary/accent/ring, red → destructive, green/yellow/orange → success/warning, and the remaining named colors → secondary accents.

6.3 Platform support matrix

Target Detection Sources read Live changes Notes
macOS compile target NSColor semantic palette in the effective appearance; NSWorkspace accessibility flags distributed notifications + GPUI appearance events Real system colors, including custom accent and increased-contrast variants
Windows 11 (WinUI) compile target WinRT UISettings colors (Accent, AccentLight1–3, AccentDark1–3, Background, Foreground…), TextScaleFactor, AccessibilitySettings high-contrast; Personalize registry fallback UISettings.ColorValuesChanged Documented WinRT APIs only; gives the exact WinUI token set
GNOME portal reachable + GNOME hint portal org.freedesktop.appearance (color-scheme, accent-color, contrast, reduced-motion); gsettings fallback portal SettingChanged Portal keys are the generic-Linux baseline too
KDE kdeglobals present kdeglobals color groups + scheme hash + accent; portal for mode/accent inotify on kdeglobals Ini parser fixture-tested
Omarchy ~/.local/state/omarchy or ~/.config/omarchy marker current/theme/colors.toml + theme.name (semantic keys or ANSI color0..15); portal for contrast/reduced-motion inotify on the current dir (palette edits + symlink replacement) Invalid/missing palette → Tokyo Night fallback; role mapping in §6.2
Fallback / unknown none of the above, or reader failure bundled neutral light/dark theme manual only Also the per-field fallback for values a reader could not supply

6.4 Accent ramp engine

Every target except the fallback provides one accent color (or a stated ramp). The engine turns an accent into the state shades components need — hover, pressed, selected, disabled, subtle fills:

  • Where the platform states shades (WinUI AccentLight1–3 / AccentDark1–2, KDE scheme ramps), use them as-is.
  • Where it does not (portal accent-color, macOS custom accent beyond the base color), derive shades in OKLCH: fixed lightness/chroma offsets toward the background (hover/subtle) or foreground (pressed/strong), hue preserved.
  • All derived colors are computed once per resolve, never per frame.

6.5 Native metrics (beyond color)

Native feel is half geometry. crates/theme ships per-platform metric tables consumed by kit components: control heights and paddings, corner radii (WinUI control vs card rounding; macOS control radii), font families and base size (SF Pro / Segoe UI Variable / Adwaita Sans / Noto Sans; rem base), focus-ring policy per platform (macOS keyboard ring, WinUI two-layer ring, GTK outline), scrollbar style (overlay vs inline, widths), popup shadow/elevation, and menu/popup metrics. Values are transcribed from each toolkit's reference metrics and verified with side-by-side screenshots in Phase 2 (macOS) and Phase 4 (Windows/Linux). Prefer GPUI's .SystemUIFont resolution with per-platform fallback lists, as gpui-omarchy does, so no fonts need bundling.

6.6 Custom themes

  • ThemeDefinition TOML files: bundled presets and user files share one schema. A definition may override any role, any metric, or just the accent (accent = "#7c3aed" → full ramp derived).
  • Merge priority: platform reader → bundled preset → user file(s) → explicit runtime overrides → accessibility adjustments. mode = "system" | "light" | "dark" forces a mode.
  • Sources: --theme <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

Prefer maintained crates over hand-rolled FFI: objc2 + objc2-app-kit (macOS), windows (WinRT UISettings), zbus (portal DBus), notify (file watches), serde + toml (definitions). All behind the ThemeSource seam so the engine core stays platform-free and testable.

6.8 What we do differently from native-theme

  • Role-minimal palette aligned 1:1 with base's semantic roles — no 139-field toolkit mapping.
  • Real semantic colors per platform (NSColor / UISettings / portal), not hardcoded approximations of platform palettes.
  • Accent-ramp derivation (OKLCH) instead of hand-mapped hover/active shades.
  • Native metrics as a first-class deliverable, not an afterthought.
  • Omarchy as a first-class target; a designed neutral fallback for unknown systems.
  • Custom themes with overlay merge priority and live reload.

7. Coding standards — AGENTS.md adaptation

Create AGENTS.md at the repo root in Phase 0, adapted from the reya/signed AGENTS.md:

From reya/signed (kept nearly verbatim):

  • Correctness and clarity first; speed secondary unless specified.
  • No organizational/summarizing comments; comments explain non-obvious why only; doc comments one line, simple.
  • Prefer existing files over new small ones; extend existing impl blocks.
  • No wrapper structs around dependency APIs; call them directly.
  • Bind global handles to a local before use; no inline chained ::global(cx) calls.
  • No unwrap()/panicking indexing; propagate with ?; never silently discard errors with let _ = — propagate, .log_err(), or handle explicitly.
  • Async errors surface to the UI layer with meaningful feedback.
  • Full-word names; shadowing to scope clones in async closures.
  • GPUI section: context conventions (cx/window), Entity/WeakEntity rules, cx.spawn/background_spawn/Task lifetime rules, Render/RenderOnce, cx.listener, actions!, cx.notify()/emit/subscribe with stored Subscriptions, GPUI executor timers in tests instead of smol::Timer.
  • PR hygiene: imperative title, optional crate prefix, Release Notes: section.

Added for this project (from the GPUI Kit coding guides, adopted as normative):

  • Never invent APIs — search the ported source for real signatures before writing code.
  • Behavior/presentation boundary: crates/base keeps zero presentation; crates/kit keeps zero behavior.
  • RenderOnce for value-like controls; Entity<_State> for retained behavior; stable domain-derived ElementIds (never list indexes).
  • SharedString for retained UI text; private fields + builders/readers for behavioral seams; naming vocabulary (is_/has_ readers, with_ builders, on_ callbacks, State/Event/Delegate suffixes).
  • No raw colors/radii/px outside crates/theme.
  • Test at the lowest layer that proves the behavior; regression test before bug fixes.
  • Porting discipline: port upstream structure and naming as-is; do not "improve" while porting — improvements are separate commits after review; record every divergence in UPSTREAM.md.

8. Testing strategy

  1. Pure tests — theme resolve overlay chain, projection mapping (presets × modes), reader payload parsing (fixtures), metrics derivations, geometry math (popup clamping, scrollbar ranges).
  2. Context tests — theme global lifecycle, appearance-change handling, token re-install.
  3. UI integration tests — headless windows: pointer/keyboard activation, controlled value round-trips, focus movement and traps, disabled/readonly, event order.
  4. Example smoke — gallery launches, switches theme/preset/mode without panic.

Conventions: GPUI executor timers in tests; regression test first for bugs; screenshots only for facts the semantic tree cannot express.

9. Licensing & attribution

  • Ported base code: Apache-2.0 — preserve upstream file headers, keep a per-file provenance table in UPSTREAM.md (upstream commit → our path → changes).
  • GPUI: Apache-2.0 (dependency, not copied).
  • native-theme: inspiration only; no code copied. If that ever changes, respect its Apache/MIT/0BSD terms.
  • Docs prose: write our own. Any prose adapted from gpui-kit.com must follow CC BY 4.0 attribution — default to avoiding it.
  • This repo's own code: Apache-2.0 recommended (D6).

10. Risks & mitigations

Risk Mitigation
Zed main (pre-1.0) churns; ported upstream source may not match our pin Pin zed rev; per-unit adaptation patches recorded in UPSTREAM.md; CI catches drift early
Custom platform readers are a maintenance burden (OS updates, portal API changes) Thin readers over parseable payloads + fixture tests; graceful fallback chain (system → preset → fallback theme) so a failed read degrades to a sane default
Omarchy colors.toml key set may evolve across releases Schema now documented and verified against a shipped theme; parse tolerantly (unknown keys ignored, missing keys fall back); track upstream theme-set changes each release cycle
Portal accent-color absent on older desktops Treat as unset (spec-defined); fall back to gsettings/KDE accent, then ramp from a neutral accent
Undocumented Windows APIs drift Use documented WinRT UISettings only; registry reads as fallback
Accent-color changes may not surface via GPUI events on all platforms Re-read on window focus/appearance events; verify per platform during Phase 2/4
Review-gated porting is slower than bulk copying Accepted by design; batches in §4.2/§5 keep the sequence predictable; unit 0 makes everything after it mechanical
36 components + lazy infra is scope creep Strict one-unit review gate; Editor and Dock deferred unless requested
WASM support (upstream base examples run on wasm) is lost Desktop is the product; theme reading is desktop-only; keep ColorTokens fallbacks (D4)

11. Decision points

# Decision Status / recommendation
D1 Theme source Decided (your feedback): custom engine inspired by native-theme; no native-theme dependency
D2 Copy strategy Decided (your feedback): one component per review-gated port unit; no wholesale vendoring
D3 GPUI source Decided (your feedback): upstream zed git only, no gpui-pre — pinned rev (recommended)
D4 WASM target Not initially; desktop only
D5 Separate base + kit crates vs one crate Separate (compiler-enforced behavior/presentation boundary)
D6 License for our code Apache-2.0
D7 Facade keeps package name gpui-native-kit Yes
D8 Platform order macOS first (dev machine); then Windows 11 (WinUI), then GNOME → KDE → Omarchy (portal baseline lands with GNOME); fallback theme from day one
D9 Styled counterpart in the same unit as the behavior port Yes from unit 1 (theme engine lands in Phase 2 before any component unit)
D10 Icon sets (Material/Lucide bundling) Out of scope initially; revisit after Phase 5
D11 Preset breadth Bundled: neutral fallback light/dark (unknown systems), tokyo-night (Omarchy's default and its Omarchy fallback), plus per-platform presets for testing readers; Omarchy colors.toml files load as presets too
D12 Editor primitive Deferred; schedule after Phase 5 if wanted
D13 Accent state shades Hybrid: stated platform ramps where available (WinUI, KDE), OKLCH derivation elsewhere
D14 Custom-theme live reload Yes, via notify; the dep is justified by the custom-theme requirement

12. Immediate next steps

  1. Confirm the remaining recommendations (D4–D12) or adjust.
  2. Phase 0: workspace restructure, zed pin, AGENTS.md, UPSTREAM.md skeleton, CI, window smoke test.
  3. Port unit 0 (foundation seed) → your review.
  4. Phase 2 theme engine (model + macOS adapter + accent ramp + fallback theme) → your review.
  5. Port unit 1 (Button, styled) → your review → continue down the §4.2 list.