update plan

This commit is contained in:
2026-11-01 20:28:48 +07:00
parent be05e12d16
commit 021b907491
+33 -15
View File
@@ -1,6 +1,6 @@
# gpui-native-kit — Project Plan
Rev 7 (2026-11-01). Changes from rev 6: units 1–14 plus the data crate are landed and marked (§6, §12); per-platform elevation stacks landed (D24); §6.3 adds the migration-driven roadmap for [signed](https://git.reya.info/reya/signed) — the first downstream app — after fresh bezel and gpuikit component surveys; the migration itself is Phase H (§7). The rev 6 rules stand: 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.
Rev 8 (2026-11-01). Changes from rev 7: the signed migration step is removed — its §10 and §12 traces cleaned up; [signed](https://git.reya.info/reya/signed) stays the first downstream consumer whose screens drive §6.3 and §6.4, but porting the app stays app-side and off the kit roadmap. A new `layout` crate (§6.4, D26) bootstraps the application shell — left sidebar on, right sidebar off, center dock with drag-to-split panes — reversing the old "dock stays app-side" deferral; Phase H now covers it. The rev 7 rules stand: 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
@@ -51,17 +51,18 @@ gpui-native-kit/ # repo root = workspace root
│ ├── core/ # gpui-native-core: components every app needs
│ │ # (one cargo feature per component)
│ ├── data/ # gpui-native-data: List, Tree, Tabs, Sidebar (one cargo feature each)
│ ├── layout/ # gpui-native-layout: application layout bootstrap + center dock (§6.4)
│ └── editor/ # gpui-native-editor: created when the editor units need it (D12, §6.3 M15)
└── examples/
└── gallery/ # showcase; a page per component + theme/icon inspector
```
Advanced crates (`data` for Tree/List/Tabs/Sidebar, `charts`, `pickers`) are created on demand when their wave starts — not speculative scaffolding.
Advanced crates (`data` for Tree/List/Tabs/Sidebar, `layout` for the application shell (§6.4), `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 / …]
FAC --> FUT[data / layout / editor / …]
CORE --> T[gpui-native-theme]
CORE --> I[gpui-native-icons]
T --> P[gpui + gpui_platform — zed, pinned rev]
@@ -74,7 +75,7 @@ One-way, no cycles. Nothing depends on `gpui-component`, `gpui-kit`, `gpuikit`,
- **One feature per component** in `core` (`button`, `checkbox`, `switch`, `radio`, `toggle`, `link`, `slider`, `progress`, `avatar`, `input`, `popover`, `tooltip`, `menu`, `dialog`, `alert`, `toast`, …) and in `data` (`list`, `tree`, `tabs`, `sidebar`). `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`, …
- Facade features map to crates: `core` (default), `data`, `layout`, `editor`, …
- 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.
@@ -225,7 +226,7 @@ Each row is one review unit: build it, show it in the gallery, review, then move
| 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: Dock (signed ships its own), window-wide text selection, overlay exit animations and hover washes (dropped with the 2026-10-30 revert; only return if a genuinely simple approach appears), charts. Markdown/TextView and VirtualList gained consumers through the signed migration — promoted to §6.3 (M14, M17).
Deferred until a real need: window-wide text selection, overlay exit animations and hover washes (dropped with the 2026-10-30 revert; only return if a genuinely simple approach appears), charts. Markdown/TextView and VirtualList gained consumers through the signed migration — promoted to §6.3 (M14, M17).
### 6.1 After the core units: sizing API — ✅ landed
@@ -254,9 +255,9 @@ Deferred until a real need: bidi/RTL; prefix/suffix adornments; `Undo`/`Redo` ev
### 6.3 Migration-driven additions — signed is the first downstream app
The first real consumer is [signed](https://git.reya.info/reya/signed), a Nostr Git platform desktop client (GPL-3.0, v0.2.0) currently built on `gpui-pre` 0.3.8 + gpui-component 0.7.1 + native-theme 0.6.1. Migrating it means the zed-pinned gpui under D3, `gpui-native-theme` in place of native-theme(-gpui), and our components replacing gpui-component view by view; `signed_ui`, `workspace`, and its dock stay app-side (dock stays off the roadmap — the app owns it). Its screens drive the units below. A survey of bezel's full catalog (bezel.gallery docs, 2026-11-01) and gpuikit's showcase source shaped the shapes, not the scope — API ideas only per D15.
The first real consumer is [signed](https://git.reya.info/reya/signed), a Nostr Git platform desktop client (GPL-3.0, v0.2.0) currently built on `gpui-pre` 0.3.8 + gpui-component 0.7.1 + native-theme 0.6.1. Its screens drive the units below — the kit ships the components; porting the app stays app-side. A survey of bezel's full catalog (bezel.gallery docs, 2026-11-01) and gpuikit's showcase source shaped the shapes, not the scope — API ideas only per D15.
Wave M1 — unblocks the port; each through the review gate:
Wave M1 — each through the review gate:
| Unit | Crate | Component | Signed consumer | Survey notes |
| --- | --- | --- | --- | --- |
@@ -271,10 +272,26 @@ Wave M1 — unblocks the port; each through the review gate:
Wave M2 — completes the surface: Field wrapper (M9: label/description/error around any control — the settings dialog; lifts another §6.2 deferral), Tag (M10: removable GRASP-server and relay entries), Status dot (M11: sync-drift beads on rows), Combobox (M12: type-to-filter pickers; gpuikit's `on_create`/`keep_unmatched` blur modes are worth copying), Command palette (M13: filtered actions — gpuikit keeps the matcher caller-owned, adopt that), VirtualList in `data` (M14: a thin `uniform_list` binding for commit histories and the inbox).
Wave M3 — content, where `crates/editor` starts (D12): code view with syntax highlighting (M15 — signed renders highlighted files with tree-sitter today), then the color-coded diff view (M16), then a Markdown view (M17) if the port confirms READMEs and issue bodies need one.
Wave M3 — content, where `crates/editor` starts (D12): code view with syntax highlighting (M15 — signed renders highlighted files with tree-sitter today), then the color-coded diff view (M16), then a Markdown view (M17) if a consumer confirms READMEs and issue bodies need one.
Survey notes — worth adopting later, not now: bezel's hover card (profile previews), skeleton rows, breadcrumb, pagination, sortable lists, follow-scroll; gpuikit's table (stays dropped — signed's explore lists read fine as Sidebar rows), kbd chips (our Menu already renders shortcut hints), form fieldset (the Field wrapper covers it), accordion.
### 6.4 The layout crate (`crates/layout`) — application shell bootstrap
A new crate (D18: advanced crates are created on demand, not speculatively) that bootstraps the application layout the way zed's workspace and gpui-kit's dock do, with a deliberately smaller API (D26): a left sidebar (on by default), a right sidebar (off by default), and a center dock whose panes the user can drag and split. A panel can declare that it needs the right sidebar; activating it opens the sidebar automatically. [signed](https://git.reya.info/reya/signed)'s `crates/dock` — a gpui-component `DockArea` behind a custom skin — is the usage reference and the cautionary tale: its renderer seams, serialization placeholders, and zoom/collapse modes mark the complexity we skip. gpui-kit's dock and zed's editor dock are read as API-design references only (D15, D20). Facade feature `layout`; the crate depends on `core` and may compose `data` — at L3, decide whether `Tabs` gains optional close buttons + drag sources for pane headers or panes render their own header row. New `IconRole`s (panel-left/right toggles, split directions) land in the icons crate with the units that need them. Everything is theme-baked per §4.3 — no style parameters, no renderer/skin seam.
Units, each through the review gate:
| Unit | Crate | Component | Notes |
| --- | --- | --- | --- |
| L1 | layout | ResizeHandle | The drag divider primitive: a thin themed handle that resizes the adjacent region (sidebar edge, split seam). Pointer drag, double-click resets to the default fraction, splitter a11y role. Lives in `layout` — promotes to `core` if a second consumer appears (D16). |
| L2 | layout | AppLayout | The bootstrap shell: left sidebar region (on by default) + center + right sidebar region (off by default). Sidebar toggle in each sidebar's header, drag-resize widths through `ResizeHandle`; visibility and widths live on an application-owned `LayoutState` (the `InputState` pattern: `set_left_sidebar_visible` / `set_right_sidebar_visible` / `is_*` readers). The center region hosts the `Dock`. |
| L3 | layout | Dock | The center pane tree, controlled: `DockState` = `Pane { tabs, active }` or `Split { axis, ratio, a, b }`; panels are registered descriptors (`id` + view builder); add / close / activate are plain state ops; a pane emptied by closing its last tab collapses its split. Pane headers show titles, the active tab's close button, and a context menu (via core `ContextMenu`). |
| L4 | layout | Dock drag-to-split | Dragging a tab: drop on a pane's edge splits a new pane there; drop on a pane's center moves the tab into that pane; a pane emptied by the move collapses. Edge drop zones paint from theme tokens while a drag is live. Split/merge go through the same `DockState` ops L3 exposed — the drag is interaction, not a second API. |
| L5 | layout | Sidebar auto-open | Panels declare `needs_right_sidebar` on their descriptor; activating such a panel opens the right sidebar; closing it leaves visibility to the user. The gallery gains a workspace-style shell page — the bootstrap example apps copy. |
Deferred until a real need: layout persistence/serialization, pane zoom (maximize one pane), a bottom dock, collapse-to-edge docks, floating windows, drag between windows.
## 7. Milestones
| Phase | Scope | Acceptance |
@@ -286,14 +303,14 @@ Survey notes — worth adopting later, not now: bezel's hover card (profile prev
| E — overlays ✅ | Popover (11), ScrollArea (11.5), Menu (12), Tooltip + Dialog/AlertDialog + Toast/Alert (11+ row) | Escape/dismiss semantics and focus behavior verified per overlay |
| E+ — motion | Unit 14 (§6): per-component passes, each a few lines on gpui's own primitives — forwarding, Switch thumb, Toast slide, Dialog scrim, Progress cap | ✅ Landed (2026-10-30), each pass reviewed in the gallery; reduce-motion snaps gpui-side with the flag forwarded |
| F — platforms + data | ✅ Landed (2026-10-30): `crates/data` (List, Tree, Tabs, Sidebar), the Linux theme sources (Omarchy/KDE/GNOME), the Windows source, the Segoe Fluent + freedesktop icon resolvers, plus per-platform elevation stacks (D24, 2026-11-01) | Remaining acceptance: side-by-side gallery screenshots — a Linux run (Omarchy/KDE/GNOME) and the first Windows hardware build |
| G — migration additions (§6.3) | Waves M1–M2: Badge, Select, Segmented, Context menu, Empty state, password + multiline input, Avatar image, then M9+ in signed's port order → 0.1 | Each unit through the review gate; each wave unblocks a real signed screen |
| H — signed migration | Port [signed](https://git.reya.info/reya/signed) to `gpui-native-kit`: the zed-pinned gpui replaces gpui-pre 0.3.8, `gpui-native-theme` replaces native-theme(-gpui) 0.6.1, our components replace gpui-component 0.7.1 view by view behind `signed_ui` | One screen per pass, reviewed side by side against the gpui-component build; no regressions in identity, explore, repository, sync, or settings flows |
| G — migration additions (§6.3) | Waves M1–M2: Badge, Select, Segmented, Context menu, Empty state, password + multiline input, Avatar image, then M9+ → 0.1 | Each unit through the review gate; each wave unblocks a real signed screen |
| H — layout crate (§6.4) | ResizeHandle, AppLayout bootstrap (left sidebar on, right sidebar off, center dock), Dock with drag-to-split, panel-declared sidebar auto-open | Each unit through the review gate; the gallery workspace page doubles as the bootstrap example apps copy |
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).
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, and dock/sidebar state transitions (split, close, collapse, auto-open); gallery smoke run. GPUI executor timers in tests. Only meaningful tests — state semantics, a11y mapping, invariants (AGENTS.md Tests rule).
## 9. Licensing
@@ -310,10 +327,10 @@ Pure tests for resolve/overlay chain, payload parsing (fixtures), icon resolutio
| --- | --- |
| 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 |
| The dock is the most interaction-heavy surface (drag, split, drop zones) | Small opinionated API (D26): a controlled pane tree with edge drop zones only; serialization, zoom, and floating windows stay out; each unit through the review gate |
| 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) |
| The signed migration crosses gpui distributions (gpui-pre 0.3.8 → zed pinned rev) | Port incrementally behind the `signed_ui` seam; a kit-side unit lands before its screen ports (§7 Phase G → H); the gallery doubles as the API-parity reference |
| "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 |
@@ -329,7 +346,7 @@ Pure tests for resolve/overlay chain, payload parsing (fixtures), icon resolutio
| 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 the editor units start — the signed migration's file viewing is the trigger (§6.3 M15–M17) |
| D12 | Editor deferred; `crates/editor` is created when the editor units start — file viewing (§6.3 M15–M17) is the trigger |
| 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 bezel and gpuikit conventions where they fit (D20), always simplified — never mirrored 1:1; small utilities may be adapted with attribution |
@@ -343,6 +360,7 @@ Pure tests for resolve/overlay chain, payload parsing (fixtures), icon resolutio
| D23 | The old Toast split into `Alert` + `Toast` (2026-10-31): one component can't be both a persistent page callout and a fleeting bottom-center nudge — different lifetime (persists until acted on vs auto-dismisses), different placement (inline in flow vs floating bottom center), different content shape (title + message + severity tint vs one line + icon + action). Alert is fully controlled — the app renders it while the condition holds and its close button reports intent; Toast keeps the store + timers. Toast drops severity and titles: Adwaita toasts are single-line nudges, and severity belongs to the banners that stay on the page |
| D24 | Shadows follow the platform's design language (2026-11-01): in-app shadows are painted by gpui, so "native" means each platform's shadow *values* — `Elevation` is a two-layer key + ambient stack and `resolve` picks it per `SourceKind` (macOS soft and layered at 12% alpha, Windows tight with more offset, Linux compact and darker; one shared dark stack where borders do the separation work). Static design constants, never OS-read (no platform API states shadow values); replaces the Alert's light-mode alpha hack, which was a single-profile workaround |
| D25 | Components fetch, never scale (2026-11-01): every derived metric a component kept re-deriving from root metrics — dense label text (`×0.8/0.85`), dense inline icons (`×0.75`), dense horizontal padding (`×0.5/0.75/2`), plate insets (`0.125/0.25/0.375 rem`), strip band heights (`×1/1.15/1.35`) — moved into the per-size-class `ControlMetrics` table (`caption_size`, `dense_icon_size`, `dense_padding_x`, `plate_inset`, `strip_height`, plus `indicator_size` for checkboxes, radios, and switch knobs), computed once in `with_root_font_size`. The table stays role-based — one value per concept per size class, shared by every component — not gpuikit's per-component token layers (D22). Component-unique geometry that has no second consumer keeps its ratios over root metrics (switch track length, slider knob, avatar initials, tree indent). Two icon roles are fetch-only: content icons (alert info, segmented options) take `icon_size` — never smaller than the label — and indicator glyphs (checks, chevrons, close buttons) take `dense_icon_size` |
| D26 | Application layout is a crate, not app-side code (2026-11-01): `crates/layout` bootstraps the shell — left sidebar (on by default), right sidebar (off), and a center dock whose panes drag-to-split — reversing the old "dock stays app-side" deferral. Prompted by signed's `crates/dock` (a gpui-component `DockArea` behind a custom skin): it proves the demand and marks the complexity we skip — no renderer/skin seam (one theme-baked appearance), no serialization, no zoom/collapse modes, no floating windows, no JS host. The API stays smaller than gpui-kit's dock and zed's editor dock (both D15/D20 references, read only): an application-owned `LayoutState`, a controlled pane tree, registered panel descriptors, and a handful of ops (split / close / activate / toggle sidebar); a panel declares `needs_right_sidebar` and the layout opens it on activation |
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.
@@ -355,7 +373,7 @@ Dropped from rev 3: D2, D5, D9 (port-era decisions); the review gate returns in
5. ~~Phase E overlays~~ ✅ — Popover (unit 11), ScrollArea (unit 11.5), Menu (unit 12), Tooltip + Dialog/AlertDialog + Toast (unit 11+ row; Sheet dropped). Phase gate held: escape/dismiss semantics and focus behavior per overlay.
6. ~~Unit 14 — motion, per component (§6 row 14, D21)~~ ✅ Landed in five passes (2026-10-30): (a) theme `reduce_motion` → `cx.set_reduce_motion` in `reapply`; (b) Switch thumb `with_spring`; (c) Toast 8 px slide-in; (d) Dialog scrim enters with the panel; (e) Progress 30 fps cap. Exits and hover washes stay deferred (§6 deferred list).
7. ~~Phase F, data first~~ ✅ data complete (2026-10-30) — `crates/data` opened with List, then Tree, Tabs, Sidebar (the dead `sheet` feature dropped from core along the way; Table dropped from the roadmap). Also extracted `crates/utils` (2026-10-30): `h_flex`/`v_flex` (core's `common` re-exports them), `focus_handle` (the namespaced keyed handle, 8 call sites), `focus_ring` (7 sites), `adjacent_selectable` + `nav_key` (4 sites), and the selectable-row paint helpers `selectable_label_color`/`paint_selectable_row` (3 sites, reading `&Theme` directly — no intermediate color struct), plus the `StyledExt` trait (`refine_style` — adapted from gpui-kit `base`, D15 — replacing the per-component `style().refine` pattern; 5 sites) — `core` and `data` depend on it; the facade doesn't re-export it. **Linux theme sources landed (2026-10-30)**: Omarchy (colors.toml parse + state-dir detection + inotify on `current`), KDE (kdeglobals `[Colors:*]` + AccentColor + `~/.config` inotify), GNOME/generic (portal `ReadAll` + `SettingChanged` via zbus 5 blocking on its own thread, gsettings fallback with the libadwaita accent table) — `SourceKind::Omarchy/Kde/Gnome`, detection cascade Omarchy → KDE → portal → fallback, all parsers fixture-tested on every OS and the Linux build compile-checked via `cargo check --target x86_64-unknown-linux-gnu`. **Windows source + icon resolvers landed (2026-10-30)**: `SourceKind::Windows` (WinRT `UISettings` + `Personalize` fallback + `ColorValuesChanged`/`HighContrastChanged`, §5.3), the Segoe Fluent codepoint renderer, and the freedesktop theme walker (§4.4); the WinRT surface was verified against the real `windows` 0.62 bindings via a standalone cross-check (gpui's own manifest build needs llvm-rc from macOS). Remaining Phase F: **side-by-side gallery screenshots per platform** — Omarchy/KDE/GNOME need a Linux run, Windows needs its first hardware build. NumberInput stays deferred until needed; multiline landed as §6.3 M7 (2026-11-01).
8. Then — Phase G is the migration additions (§6.3): Badge ✅, Select ✅, Segmented control ✅, Context menu ✅, Empty state ✅, password input ✅, multiline textarea ✅, then Avatar image (M8) — each unblocks a concrete signed screen. Pickers (Calendar/DatePicker, TimeField, ColorPicker) stay decided-at-unit-time. `crates/editor` starts when the port reaches file viewing (M15).
9. Then — Phase H, the signed migration itself (§7): theme swap first (native-theme → `gpui-native-theme`), then views one at a time, keeping the gpui-component build side by side until parity.
8. Then — Phase G is the migration additions (§6.3): Badge ✅, Select ✅, Segmented control ✅, Context menu ✅, Empty state ✅, password input ✅, multiline textarea ✅, then Avatar image (M8) — each unblocks a concrete signed screen. Pickers (Calendar/DatePicker, TimeField, ColorPicker) stay decided-at-unit-time. `crates/editor` starts when file viewing (M15) is reached.
9. Then — the layout crate (§6.4): ResizeHandle, the AppLayout bootstrap, the center Dock with drag-to-split, and panel-declared sidebar auto-open — each through the review gate, with signed's `crates/dock` as the usage reference and the gallery workspace page as the example. Order against M9+ is decided when the crate starts.
Batched polish candidates between units: scrollbar auto-hide + horizontal thumb, `Undo`/`Redo` menu events.