add button
This commit is contained in:
+36
-18
@@ -1,6 +1,6 @@
|
||||
# gpui-native-kit — Project Plan
|
||||
|
||||
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.
|
||||
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
|
||||
|
||||
@@ -12,12 +12,12 @@ Consumption is flexible: small focused crates, per-component cargo features, and
|
||||
|
||||
| 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 |
|
||||
| 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 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.
|
||||
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)
|
||||
|
||||
@@ -78,11 +78,17 @@ One-way, no cycles. Nothing depends on `gpui-component`, `gpui-kit`, `gpuikit`,
|
||||
|
||||
### 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.
|
||||
- **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.
|
||||
- **APIs are our own**, shaped by what our components need; inspiration from other crates is distilled, never copied 1:1.
|
||||
- **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.
|
||||
|
||||
@@ -178,16 +184,26 @@ Native feel is half geometry. Per-platform tables for: control heights and paddi
|
||||
|
||||
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 (written from scratch)
|
||||
## 6. Component roadmap (review units, in order)
|
||||
|
||||
| Wave | Crate | Components |
|
||||
| --- | --- | --- |
|
||||
| 1 — controls | core | Button, Checkbox, Switch, Radio + RadioGroup, Toggle, Link, Slider, Progress, Avatar; plus the `IconRole` catalog + Lucide fallback |
|
||||
| 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`. |
|
||||
| 3 — overlays | core | Popover, Tooltip, Menu, Dialog, AlertDialog, Sheet, Toast |
|
||||
| 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) |
|
||||
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.
|
||||
|
||||
@@ -197,7 +213,7 @@ Deferred until a real need: Markdown/TextView, Dock, VirtualList, window-wide te
|
||||
| --- | --- | --- |
|
||||
| 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 | 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 |
|
||||
| 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 |
|
||||
@@ -213,6 +229,7 @@ Pure tests for resolve/overlay chain, payload parsing (fixtures), icon resolutio
|
||||
|
||||
- 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.
|
||||
@@ -244,16 +261,17 @@ Pure tests for resolve/overlay chain, payload parsing (fixtures), icon resolutio
|
||||
| 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 porting, no UI-toolkit dependencies; inspiration only, always simplified — never mirrored 1:1 |
|
||||
| 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). D10 (no icon bundling) is superseded by D19.
|
||||
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. Then wave 1 controls (Phase C).
|
||||
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).
|
||||
|
||||
Reference in New Issue
Block a user