update plan

This commit is contained in:
2026-10-21 08:49:01 +07:00
parent 6f35c86afc
commit 0d4acf5b65
+52 -26
View File
@@ -10,6 +10,13 @@ through its official `native-theme-gpui` connector.
> mapping). Going through GPUI-Kit first makes that mapping unnecessary: the > mapping). Going through GPUI-Kit first makes that mapping unnecessary: the
> connector already projects native themes onto gpui-component's `Theme`, and > connector already projects native themes onto gpui-component's `Theme`, and
> every call site reads `cx.theme()` from gpui-component directly. > every call site reads `cx.theme()` from gpui-component directly.
>
> **Revision 2.** Full replacement, no hybrids: no custom UI element is kept
> or maintained after Phase A. Earlier hybrid positions in this file (keep
> `ui::message` where it fits, possibly keep nav composites, keep an icon
> mapping shim) are dropped — kit elements are adopted outright, chat-specific
> composition moves into `chat_ui`, and the only coop-owned visuals left are
> brand SVG assets rendered through the kit `Icon`.
## 1. What the git history shows ## 1. What the git history shows
@@ -42,6 +49,8 @@ graph LR
components and moves all color reads onto gpui-component's `Theme` components and moves all color reads onto gpui-component's `Theme`
(`ThemeColor`, 139 semantic fields). The app runs on gpui-component's (`ThemeColor`, 139 semantic fields). The app runs on gpui-component's
built-in light/dark palettes at the end of Phase A; `crates/theme` is gone. built-in light/dark palettes at the end of Phase A; `crates/theme` is gone.
At the end of Phase A `crates/ui` contains no UI elements: every element
comes from the kit, and only app utilities survive there (§4.4).
**Phase B** adds `native-theme-gpui`, which installs native OS themes (or any **Phase B** adds `native-theme-gpui`, which installs native OS themes (or any
of 16 bundled presets) into that same `Theme`. No call site changes — the of 16 bundled presets) into that same `Theme`. No call site changes — the
@@ -69,15 +78,15 @@ crates.
|---|---|---| |---|---|---|
| `menu/` (popup, dropdown, item) | 983 | `menu::{PopupMenu, PopupMenuItem, DropdownMenu, ContextMenu}` | | `menu/` (popup, dropdown, item) | 983 | `menu::{PopupMenu, PopupMenuItem, DropdownMenu, ContextMenu}` |
| `dock/` (mod, panel) | 1,032 | `dock` (entity-backed DockArea / panels) | | `dock/` (mod, panel) | 1,032 | `dock` (entity-backed DockArea / panels) |
| `motion.rs` | 668 | keep — app-level animation, no theme dependency | | `motion.rs` | 668 | keep — animation utility, not an element; no theme dependency |
| `notification.rs` | 594 | `notification::Notification` + `WindowExt::push_notification`; placement via `Theme.notification` | | `notification.rs` | 594 | `notification::Notification` + `WindowExt::push_notification`; placement via `Theme.notification` |
| `button.rs` | 581 | `button::{Button, ButtonGroup, Toggle, DropdownButton}` | | `button.rs` | 581 | `button::{Button, ButtonGroup, Toggle, DropdownButton}` |
| `message.rs` | 553 | evaluate `message::{Message, Bubble, Attachment, MessageScroller}`; keep as app composite where it fits better | | `message.rs` | 553 | `message::{Message, Bubble, MessageGroup, MessageScroller}` (avatar/header/content/footer slots); author grouping, date separators and receipts move into `chat_ui` as app code |
| `avatar.rs` | 445 | `avatar::Avatar` | | `avatar.rs` | 445 | `avatar::Avatar` |
| `root.rs` | 430 | framework `Root` via `gpui_kit::open_window` (owns dialogs, notifications, tooltip/menu layers, focus traps) | | `root.rs` | 430 | framework `Root` via `gpui_kit::open_window` (owns dialogs, notifications, tooltip/menu layers, focus traps) |
| `dialog.rs` | 394 | `WindowExt::open_dialog` / `AlertDialog`; custom footer buttons → Dialog's footer API | | `dialog.rs` | 394 | `WindowExt::open_dialog` / `AlertDialog`; custom footer buttons → Dialog's footer API |
| `input/` | ~290 | `input::{Input, InputState, Textarea, NumberInput}`; delete the custom styling (its `input_bg`/`selection`/`caret` reads die with it) | | `input/` | ~290 | `input::{Input, InputState, Textarea, NumberInput}`; delete the custom styling (its `input_bg`/`selection`/`caret` reads die with it) |
| `icon.rs` | 274 | `Icon` + `IconName` (Lucide); keep `ui::icon` as a thin mapping shim (see §4.3) | | `icon.rs` | 274 | kit `Icon` + `IconName` (Lucide) at every call site; the module and the coop enum are deleted (see §4.3) |
| `kbd.rs` | 270 | `Kbd` | | `kbd.rs` | 270 | `Kbd` |
| `tab/` | 385 | `tab::{Tab, TabBar, Tabs}` | | `tab/` | 385 | `tab::{Tab, TabBar, Tabs}` |
| `title_bar.rs` | 218 | `TitleBar` (keep the client-side-decoration constants) | | `title_bar.rs` | 218 | `TitleBar` (keep the client-side-decoration constants) |
@@ -85,12 +94,12 @@ crates.
| `styled.rs` | 161 | `h_flex`/`v_flex`/`StyledExt` from the kit; drop coop-specific helpers as their users migrate | | `styled.rs` | 161 | `h_flex`/`v_flex`/`StyledExt` from the kit; drop coop-specific helpers as their users migrate |
| `switch.rs` | 157 | `switch::Switch` | | `switch.rs` | 157 | `switch::Switch` |
| `markdown.rs` | 146 | `text::TextView::markdown` (code styling via `Theme.highlight_theme`) | | `markdown.rs` | 146 | `text::TextView::markdown` (code styling via `Theme.highlight_theme`) |
| `nav.rs` + `nav_item.rs` | 268 | `sidebar::{Sidebar, SidebarMenu, …}`; if the coop layout doesn't fit, keep as an app composite on top of kit primitives — decide during the slice | | `nav.rs` + `nav_item.rs` | 268 | `sidebar::{Sidebar, SidebarHeader, SidebarGroup, SidebarMenu, SidebarMenuItem, SidebarFooter}`; the nav layout is adapted to the kit structure, not preserved |
| `group_box.rs` | 126 | `group_box::GroupBox` | | `group_box.rs` | 126 | `group_box::GroupBox` |
| `scroll/` | ~124 | `scroll::{Scrollbar, Scrollable}` | | `scroll/` | ~124 | `scroll::{Scrollbar, Scrollable}` |
| `resizable/` | 31 | `resizable::{h_resizable, v_resizable, resizable_panel}` | | `resizable/` | 31 | `resizable::{h_resizable, v_resizable, resizable_panel}` |
| `tooltip.rs` | 34 | `.tooltip()` | | `tooltip.rs` | 34 | `.tooltip()` |
| `indicator.rs` | 59 | `Marker`/`Badge`, or keep if neither fits | | `indicator.rs` | 59 | `marker::Marker` / `badge::Badge`; the custom module is deleted either way |
| `window_ext.rs` | 90 | `gpui_kit::component::WindowExt` | | `window_ext.rs` | 90 | `gpui_kit::component::WindowExt` |
## 4. Phase A — GPUI-Kit migration ## 4. Phase A — GPUI-Kit migration
@@ -102,6 +111,10 @@ crates.
installs the framework `Root` (dialog, sheet, notification and tooltip installs the framework `Root` (dialog, sheet, notification and tooltip
layers). Delete `ui::Root` and its layers; `workspace::init` stays as the layers). Delete `ui::Root` and its layers; `workspace::init` stays as the
window's content view. window's content view.
- Register a combined `AssetSource` at app construction: coop's existing
per-platform source for `assets/brand` and images, with
`gpui_kit::assets::Assets` as the fallback for the component icon bundle
(embedded on desktop; CDN-on-demand on wasm).
- `TitlebarOptions` / client-side decoration setup stays coop's - `TitlebarOptions` / client-side decoration setup stays coop's
(`CLIENT_SIDE_DECORATION_*`, `TABBAR_HEIGHT` move to `ui`). (`CLIENT_SIDE_DECORATION_*`, `TABBAR_HEIGHT` move to `ui`).
- Keep `bind_keys` **before** `set_menus` (guide rule); coop already does. - Keep `bind_keys` **before** `set_menus` (guide rule); coop already does.
@@ -112,10 +125,12 @@ Replace one family at a time; consumers update in the same PR:
1. **Deps + bootstrap** (above). Custom components still work alongside the 1. **Deps + bootstrap** (above). Custom components still work alongside the
framework Root. framework Root.
2. **Icons** — introduce the `ui::icon` shim so later slices see kit icons 2. **Icons** — rewrite every icon call site onto kit `Icon`/`IconName` and
through the old call sites (see §4.3). delete `ui::icon` in the same PR (see §4.3). Mechanical churn: one Lucide
target per coop variant, `Icon::path` for brand marks; doing it early
settles the imports for all later slices.
3. **Small controls** — Button, Switch, Kbd, GroupBox, Avatar, Tooltip, 3. **Small controls** — Button, Switch, Kbd, GroupBox, Avatar, Tooltip,
Indicator, Popover. Popover; Indicator → `Marker`/`Badge`.
4. **Menu** — PopupMenu/DropdownMenu/ContextMenu (+ the popup-menu actions 4. **Menu** — PopupMenu/DropdownMenu/ContextMenu (+ the popup-menu actions
already imported from `gpui_base::actions`). already imported from `gpui_base::actions`).
5. **Input** — Input/InputState/Textarea; port the chat composer, dialog 5. **Input** — Input/InputState/Textarea; port the chat composer, dialog
@@ -124,26 +139,30 @@ Replace one family at a time; consumers update in the same PR:
`push_notification`; port the settings, relays, profile, new-chat, `push_notification`; port the settings, relays, profile, new-chat,
import… dialogs; the custom footers move to the Dialog footer API. import… dialogs; the custom footers move to the Dialog footer API.
7. **Scroll + Resizable + Tab** — panels and the workspace tab strip. 7. **Scroll + Resizable + Tab** — panels and the workspace tab strip.
8. **Sidebar/Nav** — Sidebar family (or keep composites, per the table). 8. **Sidebar/Nav** — port the nav onto `sidebar::{Sidebar, SidebarGroup,
SidebarMenu, …}` and delete `nav.rs`/`nav_item.rs` in the same PR.
9. **Title bar** — kit `TitleBar` + traffic-light handling. 9. **Title bar** — kit `TitleBar` + traffic-light handling.
10. **Dock** — the biggest single item (805 + 205 lines, `dock::add` 10. **Dock** — the biggest single item (805 + 205 lines, `dock::add`
registry, `DockPlacement`). Port panels onto `DockArea`; do it last so registry, `DockPlacement`). Port panels onto `DockArea`; do it last so
the panels it hosts are already kit-based. the panels it hosts are already kit-based.
11. **Message + Markdown** — TextView::markdown; Message/Bubble where they 11. **Message + Markdown** — TextView::markdown; port chat rows onto
fit, otherwise keep `ui::message` as an app composite (it is domain `Message`/`Bubble`/`MessageGroup` + `MessageScroller`. Chat-specific
layout, allowed by the guides' app-component layer). composition (author grouping, date separators, receipts) is app code in
12. **Cleanup** — delete the replaced modules; decide what `crates/ui` still `chat_ui`, built from kit elements — `crates/ui` keeps no message
is (see §4.4). component.
12. **Cleanup** — delete the replaced modules; `crates/ui` ends as a
utilities-only crate (see §4.4).
### 4.3 Icons ### 4.3 Icons
coop's `IconName` has 128 variants backed by `assets/icons/*.svg`; the kit's No mapping shim and no coop `IconName` enum: every call site uses the kit's
`IconName` (Lucide) has 106. Keep `ui::icon` as a mapping module: a `Icon` + `IconName` (Lucide) directly. During slice 2, give each of coop's
coop-`IconName` enum (same variant names) that resolves to the kit `IconName` 128 variants a Lucide target — exact equivalent where one exists, nearest
where a Lucide equivalent exists, and to a custom asset path (coop's bundled match otherwise, accepting the visual difference. The only coop-owned SVGs
SVGs) where it doesn't (brand marks, a few specials). Most `Icon::new(...)` that survive are true brand/product marks under `assets/brand`, rendered
call sites then keep compiling with import changes only. Delete through the kit element with `Icon::path("brand/….svg")` (the kit `Icon`
`assets/icons` entries that get Lucide equivalents; keep `assets/brand`. accepts any asset path). Delete `assets/icons` entirely once the last call
site is mapped.
### 4.4 Theme removal ### 4.4 Theme removal
@@ -188,11 +207,14 @@ call sites then keep compiling with import changes only. Delete
`colors.rs`, `palette.rs`, `brand.rs`, `appearance.rs`, `paint.rs`, `colors.rs`, `palette.rs`, `brand.rs`, `appearance.rs`, `paint.rs`,
`color.rs`, `syntax.rs`, `notification.rs`, `scrollbar_mode.rs`, `color.rs`, `syntax.rs`, `notification.rs`, `scrollbar_mode.rs`,
`platform_kind.rs`; `platform_kind.rs`;
- move to `crates/ui` unchanged: `typography.rs` (`TextStyle`/`Typeset`, move to `crates/ui` unchanged: `typography.rs` (`TextStyle`/`Typeset`,
~200 call sites — retained deliberately as coop's type ladder), ~200 call sites — retained deliberately as coop's type ladder),
`layout.rs` (spacing constants, radius accessors), `geometry.rs` `layout.rs` (spacing constants, radius accessors), `geometry.rs`
(`Edges`/`Side`/`AxisExt`), and the three `CLIENT_SIDE_DECORATION_*` (`Edges`/`Side`/`AxisExt`), and the three `CLIENT_SIDE_DECORATION_*`
constants; constants;
- end state: `crates/ui` is utilities-only — `motion.rs`, `typography.rs`,
`layout.rs`, `geometry.rs`, the CSD constants and the combined asset
source. Every element module is deleted; nothing custom is maintained;
- `AppearanceMode` (System/Light/Dark, serde-persisted) moves into - `AppearanceMode` (System/Light/Dark, serde-persisted) moves into
`crates/settings`. `crates/settings`.
- The gpui-base projection coop did in `sync_base` is now the framework's job - The gpui-base projection coop did in `sync_base` is now the framework's job
@@ -262,7 +284,11 @@ to native targets; on web, apply the fallback preset (or extend
- dock panels move, split and zoom; sidebar resizes; scrollbars behave in - dock panels move, split and zoom; sidebar resizes; scrollbars behave in
Hover/Always/Scrolling modes; Hover/Always/Scrolling modes;
- markdown code blocks render through `TextView` with syntax colors from - markdown code blocks render through `TextView` with syntax colors from
`Theme.highlight_theme`. `Theme.highlight_theme`;
- every icon renders from the kit Lucide bundle, and brand marks render
via `Icon::path` on desktop and web (on wasm the kit bundle is fetched
from CDN on demand; coop brand assets still come from coop's own
source).
- End of Phase B: - End of Phase B:
- macOS: OS light/dark follows; pinned modes; accent changes picked up at - macOS: OS light/dark follows; pinned modes; accent changes picked up at
next launch (documented limitation — no `watch` feature); next launch (documented limitation — no `watch` feature);
@@ -276,11 +302,11 @@ to native targets; on web, apply the fallback preset (or extend
| Risk | Mitigation | | Risk | Mitigation |
|---|---| |---|---|
| Scale: ~8.7k lines of custom UI, ~140 `ui::` imports, ~340 token reads | Slices in §4.2 each compile; `crates/ui` acts as the facade during the migration so consumer churn is incremental | | Scale: ~8.7k lines of custom UI, ~140 `ui::` imports, ~340 token reads | Slices in §4.2 each compile; `crates/ui` acts as the facade during the migration so consumer churn is incremental, then shrinks to utilities (§4.4) |
| Dock port (largest, most stateful) | Scheduled last; panels are kit-based by then; DockArea owns layout state coop currently hand-rolls | | Dock port (largest, most stateful) | Scheduled last; panels are kit-based by then; DockArea owns layout state coop currently hand-rolls |
| gpui-component on wasm | Spike in §6 before committing; fallback would be staying on 0.7.1 desktop-only and parking the web target | | gpui-component on wasm | Spike in §6 before committing; fallback would be staying on 0.7.1 desktop-only and parking the web target |
| Visual identity changes (shadcn-style stock palette in Phase A) | Accepted — requirement says the current palette need not survive; Phase B replaces it with native themes anyway | | Visual identity changes (shadcn-style stock palette in Phase A; kit Message/Sidebar look replacing coop's chat/nav styling) | Accepted — requirement says the current palette need not survive; Phase B replaces it with native themes anyway |
| Icon gaps (coop 128 vs Lucide 106) | Mapping shim keeps coop names; unmatched variants fall back to bundled SVGs | | Icon gaps (coop 128 vs Lucide 106) | Nearest-Lucide target per variant in slice 2; only true brand marks stay as coop assets via `Icon::path` |
| API drift between coop's wrappers and the kit (e.g. `Sizable`, `Disableable`, callback shapes) | Per the coding guides: never invent APIs; verify each component page / source signature during its slice | | API drift between coop's wrappers and the kit (e.g. `Sizable`, `Disableable`, callback shapes) | Per the coding guides: never invent APIs; verify each component page / source signature during its slice |
| `TextStyle` ladder is a deliberate holdout from rem-based text | Documented exception; sizes are absolute points and don't claim to zoom. Revisit only if the kit's text scale is adopted later | | `TextStyle` ladder is a deliberate holdout from rem-based text | Documented exception; sizes are absolute points and don't claim to zoom. Revisit only if the kit's text scale is adopted later |
| native-theme-gpui version coupling | Pin `gpui-kit = "=0.7.1"`-era set (0.7.1, gpui-pre =0.3.8) matching the connector's verified table; bump all together | | native-theme-gpui version coupling | Pin `gpui-kit = "=0.7.1"`-era set (0.7.1, gpui-pre =0.3.8) matching the connector's verified table; bump all together |