diff --git a/docs/native-theme-migration.md b/docs/native-theme-migration.md deleted file mode 100644 index 1240bfc2..00000000 --- a/docs/native-theme-migration.md +++ /dev/null @@ -1,371 +0,0 @@ -# Migrate the UI to GPUI-Kit, then native-theme - -> **Status: Phase A and Phase B are implemented** (on the working tree, -> uncommitted). The app builds against `gpui-kit` (resolved 0.7.3 / -> gpui-pre 0.3.9), every UI element comes from the kit, and `crates/theme` is -> deleted. -> -> **Phase B** adopted the OS theme through `native-theme-gpui` 0.6.1 (see -> below). `desktop` reads `SystemTheme::from_system()` at boot — before the -> window opens — and calls `apply_system_theme`, then honors the persisted -> appearance mode on top of it; a `watch`-feature subscription re-applies the -> OS theme at runtime via `native_theme::watch::on_theme_change`. Where the -> OS cannot be read, the boot logs a warning and the kit palette remains. -> The optional preset picker was skipped, matching the signed reference -> (its adoption removed its theme pickers — the connector owns fonts, -> radius and geometry). Web is untouched: `native-theme` is a desktop-only -> dependency and the wasm32 failure set is byte-for-byte the pre-existing -> baseline. -> -> **Follow-up (also on the working tree):** the last custom modules in -> `crates/ui` are gone — `motion`, `typography`, `layout` and `styled` were -> deleted and every call site now reads gpui-kit tokens directly (`text_xs` / -> `text_sm`, `cx.theme().radius` / `radius_lg`, `gap_2`, `Separator`). The -> dock wears the previous skin again, ported from the `signed` reference -> implementation onto the kit dock engine (`workspace::dock`: pill tabs, the -> 44px bar that doubles as the title bar, window controls, collapse buttons, -> chromeless sidebar group). The avatar with the pixel fallback is back -> (`ui::Avatar`): user image when set, deterministic pixel art from the -> profile/group seed otherwise, also as the image-load fallback. Desktop -> traffic lights recenter for the 44px bar (`workspace::TAB_BAR_HEIGHT`). -> Icons are vendored in `assets/icons/` (signed's set, filled from the kit -> bundle where coop needs more) and regression-tested in -> `crates/assets/tests`. - -Plan to move coop's custom UI elements onto [GPUI-Kit](https://gpui-kit.com) -(`gpui-kit` 0.7.1), delete the custom theme implementation (`crates/theme`), -and then integrate [`native-theme`](https://github.com/tiborgats/native-theme) -through its official `native-theme-gpui` connector. - -> **Revision.** This supersedes the earlier approach in this file (depend on -> the `native-theme` core crate and hand-write a `ResolvedTheme` → coop token -> mapping). Going through GPUI-Kit first makes that mapping unnecessary: the -> connector already projects native themes onto gpui-component's `Theme`, and -> 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 - -- `516eb0e` — *move gpui-components to ui crate*: the UI crate **was** the - upstream component library, vendored into `crates/ui`. Call sites used - library components and its theme vocabulary - (`cx.theme().elevated_surface_background`, `surface_background`, - `border_variant`, …). -- `0c1c23f` — *migrate to gpui-base (#51)*: the vendored library was deleted - and replaced by the `gpui-base` crate plus hand-written wrappers (Button, - Switch, Dialog, Input, Dock, Menu, Notification, …) and the custom - `crates/theme`. - -"Migrate like before" therefore means: go back to the upstream component -library — this time as a dependency (`gpui-kit`), not vendored — and let it -own the theme. - -## 2. Strategy - -```mermaid -graph LR - A[Phase A: GPUI-Kit] --> B[Phase B: native-theme-gpui] - A1[A1 Bootstrap
init + open_window + Root] --> A2[A2 Component swap
~20 custom modules] - A2 --> A3[A3 Theme removal
cx.theme = gpui-component] - A3 --> B1[B1 Connector
apply_system_theme] - B1 --> B2[B2 Appearance + presets
settings wiring] -``` - -**Phase A** replaces the custom elements with `gpui_kit::component` -components and moves all color reads onto gpui-component's `Theme` -(`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. -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 -of 16 bundled presets) into that same `Theme`. No call site changes — the -connector is verified against exactly the versions Phase A pins. - -### Target dependency set (exact versions) - -| Crate | Version | Note | -|---|---|---| -| `gpui-kit` | 0.7.1 | facade: `gpui_kit::*` = GPUI (`gpui-pre`), `gpui_kit::component` (gpui-component 0.7.1), `gpui_kit::base` (gpui-base 0.7.1), `gpui_kit::assets` | -| `gpui-pre` | =0.3.8 | already pinned; gpui-kit 0.7.1 requires it | -| `native-theme-gpui` | 0.6 | Phase B; verified against gpui-kit 0.7.1 / gpui-pre 0.3.8; needs Rust ≥ 1.95 (toolchain is 1.98) | - -Do not jump to `gpui-kit` 0.8: `native-theme-gpui` 0.6.1 pins 0.7.1, and a -gpui-component patch release has broken the connector before. - -## 3. Current custom UI inventory - -`crates/ui` is ~8,700 lines across 34 files. Consumers import it at ~138 -sites (`workspace` 14 files, `chat_ui`, `nip29_ui`, `nip4e`, `settings`, -`desktop`, `web`); theme tokens are read at ~340 sites across the same -crates. - -| Custom module | Lines | GPUI-Kit replacement | -|---|---|---| -| `menu/` (popup, dropdown, item) | 983 | `menu::{PopupMenu, PopupMenuItem, DropdownMenu, ContextMenu}` | -| `dock/` (mod, panel) | 1,032 | `dock` (entity-backed DockArea / panels) | -| `motion.rs` | 668 | keep — animation utility, not an element; no theme dependency | -| `notification.rs` | 594 | `notification::Notification` + `WindowExt::push_notification`; placement via `Theme.notification` | -| `button.rs` | 581 | `button::{Button, ButtonGroup, Toggle, DropdownButton}` | -| `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` | -| `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 | -| `input/` | ~290 | `input::{Input, InputState, Textarea, NumberInput}`; delete the custom styling (its `input_bg`/`selection`/`caret` reads die with it) | -| `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` | -| `tab/` | 385 | `tab::{Tab, TabBar, Tabs}` | -| `title_bar.rs` | 218 | `TitleBar` (keep the client-side-decoration constants) | -| `popover.rs` | 170 | `popover::Popover` | -| `styled.rs` | 161 | `h_flex`/`v_flex`/`StyledExt` from the kit; drop coop-specific helpers as their users migrate | -| `switch.rs` | 157 | `switch::Switch` | -| `markdown.rs` | 146 | `text::TextView::markdown` (code styling via `Theme.highlight_theme`) | -| `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` | -| `scroll/` | ~124 | `scroll::{Scrollbar, Scrollable}` | -| `resizable/` | 31 | `resizable::{h_resizable, v_resizable, resizable_panel}` | -| `tooltip.rs` | 34 | `.tooltip()` | -| `indicator.rs` | 59 | `marker::Marker` / `badge::Badge`; the custom module is deleted either way | -| `window_ext.rs` | 90 | `gpui_kit::component::WindowExt` | - -## 4. Phase A — GPUI-Kit migration - -### 4.1 Bootstrap - -- `desktop/main.rs` and `web/lib.rs`: `gpui_kit::init(cx)` first, then open - the window with `gpui_kit::open_window(opts, cx, |window, cx| …)`, which - installs the framework `Root` (dialog, sheet, notification and tooltip - layers). Delete `ui::Root` and its layers; `workspace::init` stays as the - 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 - (`CLIENT_SIDE_DECORATION_*`, `TABBAR_HEIGHT` move to `ui`). -- Keep `bind_keys` **before** `set_menus` (guide rule); coop already does. - -### 4.2 Component slices (each PR compiles) - -Replace one family at a time; consumers update in the same PR: - -1. **Deps + bootstrap** (above). Custom components still work alongside the - framework Root. -2. **Icons** — rewrite every icon call site onto kit `Icon`/`IconName` and - 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, - Popover; Indicator → `Marker`/`Badge`. -4. **Menu** — PopupMenu/DropdownMenu/ContextMenu (+ the popup-menu actions - already imported from `gpui_base::actions`). -5. **Input** — Input/InputState/Textarea; port the chat composer, dialog - fields, relay editor; drop the clear-button and input-styling modules. -6. **Dialog + Notification** — `open_dialog`/`AlertDialog`/ - `push_notification`; port the settings, relays, profile, new-chat, - import… dialogs; the custom footers move to the Dialog footer API. -7. **Scroll + Resizable + Tab** — panels and the workspace tab strip. -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. -10. **Dock** — the biggest single item (805 + 205 lines, `dock::add` - registry, `DockPlacement`). Port panels onto `DockArea`; do it last so - the panels it hosts are already kit-based. -11. **Message + Markdown** — TextView::markdown; port chat rows onto - `Message`/`Bubble`/`MessageGroup` + `MessageScroller`. Chat-specific - composition (author grouping, date separators, receipts) is app code in - `chat_ui`, built from kit elements — `crates/ui` keeps no message - component. -12. **Cleanup** — delete the replaced modules; `crates/ui` ends as a - utilities-only crate (see §4.4). - -### 4.3 Icons - -No mapping shim and no coop `IconName` enum: every call site uses the kit's -`Icon` + `IconName` (Lucide) directly. During slice 2, give each of coop's -128 variants a Lucide target — exact equivalent where one exists, nearest -match otherwise, accepting the visual difference. The only coop-owned SVGs -that survive are true brand/product marks under `assets/brand`, rendered -through the kit element with `Icon::path("brand/….svg")` (the kit `Icon` -accepts any asset path). Delete `assets/icons` entirely once the last call -site is mapped. - -### 4.4 Theme removal - -- **Call sites**: `theme::ActiveTheme` → `gpui_kit::component`'s `ActiveTheme` - (`cx.theme() -> &Theme`). Rewrite the ~340 token reads with the mapping - below (done per consumer crate, one slice each: `ui`, `workspace`, - `chat_ui`, `nip29_ui`, `settings` + `nip4e`). - -| coop token (`cx.theme().x`) | gpui-component `ThemeColor` | -|---|---| -| `bg` | `background` | -| `surface` | `sidebar` | -| `surface_raised` | `secondary` | -| `surface_card` | `muted` | -| `surface_dialog`, `surface_overlay` | `popover` | -| `element_hover` | `accent` (gpui-component's accent **is** the item highlight) | -| `element_active` | `list_active` | -| `border` | `border` | -| `border_faint`, `border_strong` | `border` (one border token; faintness loss accepted) | -| `ring` | `ring` | -| `text` | `foreground` | -| `text_muted`, `text_faint`, `text_dim` | `muted_foreground` | -| `cursor`, `caret` | `caret` | -| `selection` | `selection` | -| `solid` / `on_solid` | `primary` / `primary_foreground` | -| `accent`, `accent_strong` | `primary` (emphasis ink) | -| `danger` / `danger_muted` | `danger` | -| `danger_strong` | `button_danger` | -| `warning` / `warning_muted` | `warning` / `warning.alpha(≈0.15)` at the call site | -| `success` | `success` | -| `input_bg`, `selection`, `caret` inside the custom input | die with the custom Input (§3) | -| `code_text`, `code_wash` | die with `ui::markdown` → `TextView` | -| `drop_line` / `drop_target` | `drag_border` / `drop_target` | - -- **Non-color fields** move to `Theme` directly: `font_sans`/`font_mono` → - `theme.font_family`/`mono_font_family`; `shadow` → `theme.shadow`; - `scrollbar_mode` → `theme.scrollbar_mode`; notification placement → - `theme.notification` (gpui-component ships the same `NotificationSettings` - coop's was modeled on); `is_dark()` → `theme.mode`. -- **`crates/theme` disposition**: - - delete: `lib.rs` (Theme global, `AppExt`, `sync_base`, `PaletteBuilder`), - `colors.rs`, `palette.rs`, `brand.rs`, `appearance.rs`, `paint.rs`, - `color.rs`, `syntax.rs`, `notification.rs`, `scrollbar_mode.rs`, - `platform_kind.rs`; - - the `typography`, `layout`, `styled` and `motion` modules were NOT kept: - a follow-up revision removed them and adapted every call site to gpui-kit - tokens — the `TextStyle` ladder collapsed to `text_xs`/`text_sm` (plus - `font_semibold`/`font_medium`), the radius ladder to `cx.theme().radius` - / `cx.theme().radius_lg`, `SPACE` to `gap_2`, `divider` to the kit - `Separator`, and motion was deleted unused. `geometry.rs` and the CSD - constants never carried over (the framework Root draws the window - border); `TAB_BAR_HEIGHT` (44px, the dock tab bar that doubles as the - title bar) now lives in `workspace::dock` and `SIDEBAR_WIDTH` in - `workspace`; - - end state: `crates/ui` is `avatar.rs` + `window_drag.rs` only — see the - status note at the top of this file for the restored dock skin and - avatar (the two deliberate exceptions to "no custom elements"); - - `AppearanceMode` (System/Light/Dark, serde-persisted) moves into - `crates/settings`. -- The gpui-base projection coop did in `sync_base` is now the framework's job - (`Theme::update` / `Theme::change` keep colors, tokens and the base layer - in step per the coding guides). - -### 4.5 Appearance handling (end of Phase A) - -- `settings::apply_theme` → `Theme::change(mode, None, cx)` for a pinned - Light/Dark, `Theme::sync_system_appearance(None, cx)` for System. -- `workspace`'s `Theme::sync_system_appearance(Some(window), cx)` call stays, - now against the kit's `Theme`; window-appearance observation comes with the - framework Root/theme (verify; if not, keep the existing - `observe_window_appearance` wiring). - -## 5. Phase B — native-theme-gpui - -> **Implemented.** `native-theme` 0.6.1 (`watch`) + `native-theme-gpui` 0.6.1 -> (default-features off, `system-fonts`) are workspace deps; only `desktop` -> depends on them, so nothing enters the wasm graph. `desktop/src/main.rs` -> applies the OS theme after `settings::init` and before the window opens, -> then honors the persisted appearance mode; on read failure it logs a -> warning and keeps the kit palette (which the appearance dropdown still -> drives). A `ThemeWatcher` global holds the `watch` subscription and -> re-applies on OS theme changes through a flume channel. The optional -> preset picker (step 3) was skipped, matching the signed reference where -> adoption *removed* the theme pickers. Settings, workspace and all UI -> crates are untouched — Phase A's appearance controls work on the native -> palettes as planned. The web fallback-preset idea was dropped with it: -> web keeps the kit palette and its appearance dropdown, exactly as before. - -1. Add `native-theme-gpui = "0.6"` (pulls `native-theme`; keep default - features or trim icon features coop doesn't use). -2. Boot, after `gpui_kit::init` and before opening the window: - - ```rust,ignore - match native_theme::SystemTheme::from_system() { - Ok(sys) => native_theme_gpui::apply_system_theme(&sys, cx), - Err(error) => { - log::warn!("system theme unavailable: {error}"); - // per-platform fallback preset: macos-sonoma / kde-breeze / windows-11 - let prefs = native_theme_gpui::AccessibilityPreferences::default(); - if let Ok((theme, resolved)) = native_theme_gpui::from_preset("macos-sonoma", true, &prefs) { - native_theme_gpui::apply(theme, &resolved, &prefs, cx); - } - } - } - ``` - - `apply_system_theme` stores **both** variants and a `ThemeConfig` per - mode; afterwards the Phase A controls keep working unchanged: - `Theme::sync_system_appearance` / `Theme::change` switch between the - native palettes. -3. Optional preset picker (the same one proposed in the superseded plan): - `settings.theme_preset: Option`, a dropdown in Preferences → - Appearance; selecting one calls `from_preset(name, is_dark, &prefs)` + - `apply` for both variants. -4. Fonts, radius, scrollbar geometry and the base layer are written by the - connector (it also re-asserts them after each upstream rebuild via its - observer). Coop writes nothing theme-related itself. - -wasm: `SystemTheme::from_system` is unavailable on wasm32 — cfg-gate step 2 -to native targets; on web, apply the fallback preset (or extend -`native-theme` with a web story later; presets need no OS reader). - -## 6. Testing and QA - -- Per slice: `cargo check` workspace, `cargo clippy`, `cargo fmt --check`, - plus the affected crate's tests; UI behavior verified by hand in the - desktop app. -- **Early spike (before slice 2)**: `cargo check -p web --target - wasm32-unknown-unknown` with the `gpui-kit` dependency added. The vendored - library was wasm-capable; confirm the published crates are too before - committing to the migration. If they are not, stop and reassess — the web - target is a launch blocker. -- End of Phase A: - - all dialogs open/close, focus trap and restore works; nested - popover-in-dialog works (framework Root owns the layers); - - notifications stack and auto-hide; settings' appearance dropdown switches - Light/Dark/System with a full repaint; - - dock panels move, split and zoom; sidebar resizes; scrollbars behave in - Hover/Always/Scrolling modes; - - markdown code blocks render through `TextView` with syntax colors from - `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: - - macOS: OS light/dark follows; pinned modes; accent changes picked up at - next launch (documented limitation — no `watch` feature); - - Linux (KDE + GNOME/portal) and Windows: theme reads work or fall back to - the platform preset with a logged warning; - - preset switch renders in both modes; inputs/menus/dialogs/tooltips/ - scrollbars all paint from the native palette; - - startup shows no wrong-palette flash (apply before the first window). - -## 7. Risks - -| 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, 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 | -| 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; 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) | 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 | -| `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 | - -## 8. Out of scope - -- Migrating `motion.rs` or the panel-background image feature. -- Accessibility text scaling / reduce-motion plumbing (connector supports - it; adopt after Phase B if wanted). -- Replacing coop's composited panels (chat, screening, backup…) with kit - `Sheet`/`Settings` — revisit after the migration settles. diff --git a/script/migration/fixup.py b/script/migration/fixup.py deleted file mode 100644 index 61249c2d..00000000 --- a/script/migration/fixup.py +++ /dev/null @@ -1,98 +0,0 @@ -#!/usr/bin/env python3 -"""Phase A fix-up: route gpui_kit::component imports to their real module paths, -rewrite font-weight helpers. Run from repo root. Idempotent.""" -import re -from pathlib import Path - -# item -> full module path under gpui_kit::component (None = keep at root) -MODULE_PATHS = { - "Avatar": "avatar", - "Button": "button", - "ButtonVariants": "button", - "ButtonVariant": "button", - "ButtonCustomVariant": "button", - "ButtonGroup": "button", - "DropdownButton": "button", - "Toggle": "button", - "Notification": "notification", - "NotificationType": "notification", - "Switch": "switch", - "GroupBox": "group_box", - "GroupBoxVariants": "group_box", - "GroupBoxVariant": "group_box", - "Tab": "tab", - "TabBar": "tab", - "Scrollbar": "scroll", - "ScrollableElement": "scroll", - "Input": "input", - "InputState": "input", - "Textarea": "textarea" if False else "input", - "TextareaState": "input", - "InputEvent": "input", - "PopupMenu": "menu", - "PopupMenuItem": "menu", - "DropdownMenu": "menu", - "ContextMenu": "menu", - "Dialog": "dialog", - "DialogButtonProps": "dialog", - "AlertDialog": "dialog", - "DockArea": "dock", - "DockLayout": "dock", - "DockPlacement": "dock", - "Panel": "dock", - "PanelEvent": "dock", - "PanelHandle": "dock", - "ClosePanel": "dock", - "DockSkin": "dock", - "Spinner": "spinner", - "Tooltip": "tooltip", - "Badge": "badge", - "Marker": "marker", - "Kbd": "kbd", - "Sidebar": "sidebar", - "SidebarMenu": "sidebar", - "SidebarMenuItem": "sidebar", - "SidebarGroup": "sidebar", - "SidebarHeader": "sidebar", - "SidebarFooter": "sidebar", - "SidebarItem": "sidebar", -} - -FILES = list(Path("crates").rglob("*.rs")) - -kit_re = re.compile(r"^use gpui_kit::component::\{([^}]*)\};") - -for f in FILES: - src = f.read_text() - lines = src.split("\n") - buckets = {} - keep = [] - for line in lines: - m = kit_re.match(line.strip()) - if m: - for item in [i.strip() for i in m.group(1).split(",") if i.strip()]: - base = item.split(" as ")[0].strip() - mod = MODULE_PATHS.get(base) - buckets.setdefault(mod, []).append(item) - else: - keep.append(line) - if buckets: - imports = [] - for mod, items in sorted(buckets.items(), key=lambda kv: kv[0] or ""): - uniq = sorted(set(items)) - if mod is None: - imports.append(f"use gpui_kit::component::{{{', '.join(uniq)}}};") - else: - imports.append(f"use gpui_kit::component::{mod}::{{{', '.join(uniq)}}};") - # insert after the last existing `use` at top or just append imports where first import was - keep[0:0] = imports - src = "\n".join(keep) - - # font weight helpers (gpui_base::StyledExt has none) - src = src.replace(".font_semibold()", ".font_weight(gpui::FontWeight::SEMIBOLD)") - src = src.replace(".font_medium()", ".font_weight(gpui::FontWeight::MEDIUM)") - src = src.replace(".font_normal()", ".font_weight(gpui::FontWeight::NORMAL)") - - if src != f.read_text(): - f.write_text(src) - print("fixed", f) diff --git a/script/migration/sweep.py b/script/migration/sweep.py deleted file mode 100644 index 59621979..00000000 --- a/script/migration/sweep.py +++ /dev/null @@ -1,189 +0,0 @@ -#!/usr/bin/env python3 -"""Phase A sweep: mechanical import + token rewrites toward gpui-kit. - -Run from repo root: python3 script/migration/sweep.py -Idempotent; only touches consumer crates. -""" -import re -import sys -from pathlib import Path - -FILES = [ - "crates/settings/src/lib.rs", - "crates/nip4e/src/lib.rs", - "crates/nip29_ui/src/lib.rs", - "crates/nip29_ui/src/message.rs", - "crates/nip29_ui/src/details.rs", - "crates/nip29_ui/src/invite.rs", - "crates/nip29_ui/src/metadata.rs", - "crates/nip29_ui/src/util.rs", - "crates/chat_ui/src/lib.rs", - "crates/chat_ui/src/text.rs", - "crates/chat_ui/src/actions.rs", - "crates/chat_ui/src/file.rs", - "crates/workspace/src/lib.rs", - "crates/workspace/src/dialogs/import.rs", - "crates/workspace/src/dialogs/join_group.rs", - "crates/workspace/src/dialogs/new_chat.rs", - "crates/workspace/src/dialogs/new_group.rs", - "crates/workspace/src/dialogs/profile.rs", - "crates/workspace/src/dialogs/relays.rs", - "crates/workspace/src/dialogs/restore.rs", - "crates/workspace/src/dialogs/screening.rs", - "crates/workspace/src/dialogs/settings.rs", - "crates/workspace/src/panels/backup.rs", - "crates/workspace/src/panels/browse.rs", - "crates/workspace/src/panels/contact_list.rs", - "crates/workspace/src/panels/greeter.rs", - "crates/workspace/src/panels/inbox.rs", - "crates/workspace/src/sidebar/group_view.rs", - "crates/workspace/src/sidebar/header.rs", - "crates/workspace/src/sidebar/inbox.rs", - "crates/workspace/src/sidebar/list.rs", - "crates/workspace/src/sidebar/mod.rs", - "crates/workspace/src/sidebar/onboarding.rs", - "crates/workspace/src/sidebar/rows.rs", - "crates/workspace/src/sidebar/search.rs", -] - -KIT = "gpui_kit::component" - -# ui:: items that move to the kit's component root -KIT_ITEMS = { - "Icon", "IconName", "Sizable", "Size", "Disableable", "Selectable", - "StyledExt", "h_flex", "v_flex", "WindowExt", - "Notification", "NotificationType", - "Button", "ButtonVariants", "ButtonVariant", "ButtonCustomVariant", "IconPosition", - "Switch", "Avatar", "GroupBox", "GroupBoxVariants", "GroupBoxVariant", - "Tab", "TabBar", "Scrollbar", "ScrollableElement", "Tooltip", - "Input", "InputState", "Textarea", "TextareaState", "InputEvent", - "PopupMenu", "PopupMenuItem", "DropdownMenu", "ContextMenu", - "Dialog", "DialogButtonProps", "AlertDialog", - "ActiveTheme", "Theme", "ThemeMode", - "Spinner", "Kbd", "TitleBar", - "DockArea", "DockLayout", "DockPlacement", "Panel", "PanelEvent", - "PanelHandle", "ClosePanel", - "Divider", "Separator", -} - -# ui:: items that stay in the coop ui crate (utilities) -UI_ITEMS = { - "TextStyle", "Typeset", "Metrics", "base_text_size", "DEFAULT_BASE_TEXT_SIZE", - "SPACE", "CONTENT_MARGIN", "EDGE", "BUTTON_HEIGHT", "CONTROL_HEIGHT_SMALL", - "BASE_RADIUS", "bubble_radius", "surface_radius", "panel_radius", "button_radius", - "control_radius", "inset_radius", - "TABBAR_HEIGHT", "SIDEBAR_WIDTH", "TRAFFIC_LIGHT_PADDING", - "CLIENT_SIDE_DECORATION_ROUNDING", "CLIENT_SIDE_DECORATION_SHADOW", - "CLIENT_SIDE_DECORATION_BORDER", - "title_bar_drag_handlers", -} - -TOKEN_RENAMES = [ - # order matters: longest first - ("surface_raised_hover", "secondary_hover"), - ("surface_raised", "secondary"), - ("surface_dialog", "popover"), - ("surface_overlay", "popover"), - ("surface_card", "muted"), - ("surface", "sidebar"), - ("element_hover", "accent"), - ("element_active", "list_active"), - ("border_faint", "border"), - ("border_strong", "border"), - ("text_muted", "muted_foreground"), - ("text_faint", "muted_foreground"), - ("text_dim", "muted_foreground"), - ("on_solid", "primary_foreground"), - ("solid", "primary"), - ("accent_strong", "primary"), - ("accent", "primary"), - ("danger_strong", "button_danger"), - ("danger_muted", "danger"), - ("warning_muted", "warning.opacity(0.15)"), - ("drop_line", "drag_border"), - ("input_bg", "input"), - ("bg", "background"), - ("text", "foreground"), -] - -def rewrite_use(line: str): - """Rewrite one `use ui::...;` or `use theme::...;` line. Returns new lines.""" - s = line.strip() - if s.startswith("use ui::{") and s.endswith(";"): - inner = s[len("use ui::{"):-2] - items = [i.strip() for i in inner.split(",") if i.strip()] - kit, ui = [], [] - for item in items: - name = item.split(" as ")[0].strip() - (ui if name in UI_ITEMS else kit).append(item) - out = [] - if kit: - out.append(f"use {KIT}::{{{', '.join(sorted(set(kit)))}}};") - if ui: - out.append(f"use ui::{{{', '.join(sorted(set(ui)))}}};") - return out - if s.startswith("use ui::") and s.endswith(";"): - path = s[4:-1] - item = path.split("::")[-1] - if item in UI_ITEMS: - return [f"use ui::{item};"] - return [f"use {KIT}::{{{item}}};"] - if s.startswith("use theme::{") and s.endswith(";"): - inner = s[len("use theme::{"):-2] - items = [i.strip() for i in inner.split(",") if i.strip()] - kit, ui = [], [] - for item in items: - name = item.split("::")[-1] - if name == "ActiveTheme" or name in KIT_ITEMS: - kit.append(name) - else: - ui.append(name) - out = [] - if kit: - out.append(f"use {KIT}::{{{', '.join(sorted(set(kit)))}}};") - if ui: - out.append(f"use ui::{{{', '.join(sorted(set(ui)))}}};") - return out - if s.startswith("use theme::") and s.endswith(";"): - name = s[11:-1].split("::")[-1] - if name == "ActiveTheme" or name in KIT_ITEMS: - return [f"use {KIT}::{name};"] - return [f"use ui::{name};"] - return [line] - -def process(path: Path): - src = path.read_text() - lines = src.split("\n") - out = [] - for line in lines: - s = line.strip() - if s.startswith("use ui::") or s.startswith("use theme::"): - out.extend(rewrite_use(line)) - else: - out.append(line) - src = "\n".join(out) - - # theme token renames, applied to `theme().x` reads only - for old, new in TOKEN_RENAMES: - src = re.sub( - re.escape("theme()." + old) + r"(?![a-z_0-9])", - "theme()." + new, - src, - ) - - # method/type renames - src = src.replace("WindowExtension", "WindowExt") - src = src.replace(".clear_notification_by_id::<", ".remove_notification::<") - src = src.replace(".clear_notification::<", ".remove_notification::<") - src = src.replace("with_kind(NotificationKind::", "with_type(NotificationType::") - src = src.replace("NotificationKind", "NotificationType") - - path.write_text(src) - -for f in FILES: - p = Path(f) - if p.exists(): - process(p) - print("swept", f) - else: - print("MISSING", f, file=sys.stderr) diff --git a/script/migration/sweep_tokens.py b/script/migration/sweep_tokens.py deleted file mode 100644 index 545666f6..00000000 --- a/script/migration/sweep_tokens.py +++ /dev/null @@ -1,63 +0,0 @@ -#!/usr/bin/env python3 -'''One-shot sweep: replace custom ui helpers with gpui-kit tokens.''' - -import pathlib - -CRATES = pathlib.Path('crates') - -REPLACEMENTS = [ - # Text ladder -> gpui text classes (Caption2 before Caption) - ('.text_style(TextStyle::Headline)', '.text_sm().font_semibold()'), - ('.text_style(TextStyle::Body)', '.text_sm()'), - ('.text_style(TextStyle::Caption2)', '.text_xs().font_medium()'), - ('.text_style(TextStyle::Caption)', '.text_xs()'), - ('.text_style(TextStyle::Callout)', '.text_xs()'), - ('.text_style(TextStyle::Subheadline)', '.text_xs()'), - # Radius ladder -> theme tokens - ('px(ui::bubble_radius())', 'cx.theme().radius_lg'), - ('px(ui::surface_radius())', 'cx.theme().radius_lg'), - ('px(ui::panel_radius())', 'cx.theme().radius_lg'), - ('px(ui::control_radius())', 'cx.theme().radius'), - ('px(ui::button_radius())', 'cx.theme().radius'), - ('px(bubble_radius())', 'cx.theme().radius_lg'), - ('px(surface_radius())', 'cx.theme().radius_lg'), - ('px(panel_radius())', 'cx.theme().radius_lg'), - ('px(control_radius())', 'cx.theme().radius'), - ('px(button_radius())', 'cx.theme().radius'), - # Spacing -> Tailwind gap - ('gap(px(ui::SPACE))', 'gap_2()'), - # Custom divider -> kit Separator - ('divider(cx)', 'Separator::horizontal()'), - # Avatar import swap - ('use gpui_kit::component::avatar::Avatar;', 'use ui::Avatar;'), - # Import lines that are now unused (whole line, with newline) - ('use ui::{TextStyle, Typeset as _, bubble_radius, control_radius};\n', ''), - ('use ui::{TextStyle, Typeset as _, button_radius, control_radius};\n', ''), - ('use ui::{TextStyle, Typeset as _, button_radius, panel_radius};\n', ''), - ('use ui::{TextStyle, Typeset as _, control_radius};\n', ''), - ('use ui::{TextStyle, Typeset as _, button_radius};\n', ''), - ('use ui::{TextStyle, Typeset as _, surface_radius};\n', ''), - ('use ui::{TextStyle, Typeset as _};\n', ''), - ('use ui::divider;\n', ''), - ('use ui::button_radius;\n', ''), -] - - -def main() -> None: - changed = [] - for path in CRATES.rglob('*.rs'): - if str(path).startswith('crates/ui/'): - continue - text = path.read_text() - original = text - for old, new in REPLACEMENTS: - text = text.replace(old, new) - if text != original: - path.write_text(text) - changed.append(str(path)) - print('\n'.join(changed)) - print(f'{len(changed)} files changed') - - -if __name__ == '__main__': - main()