| 文件 | 最后提交记录 | 最后更新时间 |
|---|---|---|
root: Add Base window hosting and a single Kit startup entry point (#3152) ## Summary Window startup now always mounts a `gpui_base::Root`, regardless of Cargo feature unification. Base owns application content, overlay hosting, keyboard traversal, and text-selection copying. Component initialization registers its per-window presentation state as a Root plugin, so dialogs, sheets, notifications, menus, tooltips, touch selection, and window chrome remain automatic without making Base depend on Component. - Add `gpui_kit::open_window` as the standard Kit application entry point. It always mounts Base Root and returns both the window handle and content entity. - Move Root ownership and unconditional overlay hosting into `gpui-base`; `gpui_component::Root` is now a re-export. Root now renders sheet, dialog, and notification layers automatically. - Add the typed `RootPlugin` interface. Plugins are registered before window creation, instantiated independently per window, rendered in registration order, and can prepare, style, and decorate the root surface. - Keep Component presentation in a `WindowState` Root plugin while application-facing operations remain on `WindowExt`. - Make `window_border()` solely responsible for client-side window chrome. Server-decorated windows pass content through unchanged; client-decorated windows receive borders, shadow inset, rounded corners, and resize hit zones. - Remove `Root::bordered` and `Root::window_shadow_size`, plus the obsolete `root_borderless` example. - Migrate Kit examples, stories, tests, documentation, and pending 0.7.0 release notes to the new startup and Root APIs. ## Public API ### gpui-base ```rust pub trait RootPlugin: Render + Sized { fn prepare(&mut self, window: &mut Window, cx: &mut Context<Self>) {} fn style(&self, surface: &mut Stateful<Div>, window: &mut Window, cx: &mut App) {} fn decorate( &self, surface: AnyElement, root: &Root, window: &mut Window, cx: &mut App, ) -> impl IntoElement { surface } } impl Root { pub fn new( view: impl Into<AnyView>, window: &mut Window, cx: &mut Context<Self>, ) -> Self; pub fn register_plugin<V: RootPlugin>( cx: &mut App, build: fn(&mut Window, &mut Context<V>) -> V, ); pub fn plugin<V: RootPlugin>(&self) -> Option<Entity<V>>; pub fn view(&self) -> &AnyView; pub fn read<'a>(window: &'a Window, cx: &'a App) -> &'a Self; pub fn update<R>( window: &mut Window, cx: &mut App, f: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R, ) -> R; } ``` `Root` also implements `Styled`. Instance style refinements are applied after plugin defaults and therefore take precedence: ```rust impl Styled for Root { fn style(&mut self) -> &mut StyleRefinement; } ``` Register plugins during explicit application initialization, before creating windows. Re-registering a plugin type replaces its factory for future windows rather than mounting it twice. Registration does not retrofit existing roots. ### gpui-kit ```rust pub fn open_window<V: Render>( options: WindowOptions, cx: &mut App, build: impl FnOnce(&mut Window, &mut App) -> Entity<V>, ) -> Result<(AnyWindowHandle, Entity<V>)>; ``` The builder returns application content, not a Root. Kit mounts Base Root around it. ### gpui-component `pub use gpui_base::Root;` replaces the former Component-owned Root. `gpui_component::init(cx)` registers Component `WindowState` as a Root plugin. Manual layer-rendering and Root-owned dialog, sheet, and notification operations are removed. In particular, `Root::render_sheet_layer`, `Root::render_dialog_layer`, and `Root::render_notification_layer` no longer exist because Root hosts those layers automatically. Use the existing `WindowExt` operations to open and update them. ## Breaking Changes Targeted for 0.7.0; package versions remain unchanged. Use the Kit window entry point and return application content instead of constructing `Root` manually. Retain the returned content entity when direct content access is needed, because the window root is now `gpui_base::Root`: ```diff - cx.open_window(options, |window, cx| { - let content = build_content(window, cx); - cx.new(|cx| Root::new(content, window, cx)) - }) + let (window_handle, content) = + gpui_kit::open_window(options, cx, |window, cx| { + build_content(window, cx) + })?; ``` Delete manual overlay placement. `Root::render_sheet_layer`, `Root::render_dialog_layer`, and `Root::render_notification_layer` are removed because `Root` now hosts these layers automatically: ```diff - let sheet_layer = Root::render_sheet_layer(window, cx); - let dialog_layer = Root::render_dialog_layer(window, cx); - let notification_layer = Root::render_notification_layer(window, cx); - div() .child(content) - .children(sheet_layer) - .children(dialog_layer.map(|layer| deferred(layer).with_priority(1))) - .children(notification_layer) ``` Root window-chrome configuration is removed. Decoration policy comes from GPUI `WindowOptions`; `window_border()` applies client chrome only for `Decorations::Client`: ```diff - Root::bordered - Root::window_shadow_size + window_border() ``` The Root- and WindowExt-owned text-selection methods are removed in favor of `gpui_base::TextSelection`: ```diff - window.selected_text(cx) + TextSelection::selected_text(window, cx) - window.has_text_selection(cx) + TextSelection::has_selection(window, cx) - root.clear_text_selection(window, cx) - window.clear_text_selection(cx) + TextSelection::clear(window, cx) - window.end_text_selection(cx) + TextSelection::end(window, cx) ``` The remaining Component-owned Root operations are removed in favor of the corresponding `WindowExt` methods: ```diff - root.open_dialog(build, window, cx) + window.open_dialog(cx, build) - root.close_dialog(window, cx) + window.close_dialog(cx) - root.close_all_dialogs(window, cx) + window.close_all_dialogs(cx) - root.open_sheet_at(placement, build, window, cx) + window.open_sheet_at(placement, cx, build) - root.close_sheet(window, cx) + window.close_sheet(cx) - root.push_notification(notification, window, cx) + window.push_notification(notification, cx) - root.remove_notification::<T>(window, cx) + window.remove_notification::<T>(cx) - root.remove_notification1::<T>(key, window, cx) + window.remove_notification1::<T>(key, cx) - root.clear_notifications(window, cx) + window.clear_notifications(cx) ``` ## Test Plan - `cargo test -p gpui-base --lib` — 993 passed. - `cargo test -p gpui-component --lib` — 559 passed. - `cargo test -p gpui-kit --features test-support,component,assets --test root` — 6 passed. - `cargo test -p gpui-kit --features test-support,component,assets --tests` — 114 passed during the window-startup migration. - `cargo test -p gpui-kit --features test-support,component,assets --test rendering` — 2 Metal pixel checks passed. - `cargo clippy -p gpui-base -p gpui-component -p gpui-kit --all-targets --features gpui-kit/test-support -- --deny warnings` — passed. - `cargo check -p gpui-kit --no-default-features` — passed. - `cargo fmt --all --check` — passed. - `git diff --check` — passed. - `script/check-ai-recipes` — 9 published recipe fragments passed. AI-assisted changes prepared with Codex. --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> Co-authored-by: Codex <codex@openai.com> | 22 天前 | |
shell: Ship gpui-shell as an installable runtime with run, new and packaging (#3464) Makes `gpui-shell` a runtime you install and ship applications with, rather than only a development host run through `cargo run`. Releases now publish a prebuilt `gpui-shell` binary, installed applications live under `~/.gpui-shell/apps`, and an application can be packaged as the binary plus its script directory. ## What changes **One package, one command.** `gpui-component-shell` is merged into `gpui-shell` as the default `components` feature (`gpui_shell::components`). Cargo forbids the cycle a feature on a separate crate would need, so the catalog moved in. Without the feature, the runtime still does not depend on `gpui-component`, and CI checks that build. **Command line.** It runs one application per process and has no interface of its own: ```text gpui-shell run <name> | -d <dir> [--watch] [--dev] # ~/.gpui-shell/apps/<name>, or a directory gpui-shell # the application packaged beside the executable gpui-shell new <dir> # scaffold a blank application gpui-shell # with nothing packaged beside it: the help gpui-shell list # installed applications and their requested capabilities gpui-shell check | types | agents ... ``` - An installed or packaged application keeps its storage under its manifest `id`; a directory run with `-d` stays keyed by its path. - With no arguments the binary runs `Contents/Resources/app` inside a macOS bundle, or `app/` beside itself. This is what lets a `.app` or a Linux package be the binary plus a directory of scripts. - `GPUI_SHELL_HOME` replaces `~/.gpui-shell`, both for applications and for the Git dependency cache. - `agents` prints, or writes as `AGENTS.md`, development instructions for coding agents. They are generated from the running binary, including its module and component list. - The window is brought forward on launch, and Cmd-Q / Alt-F4 quits. - The manifest gains an optional `window` field: `{ "width": 360, "height": 520 }` sets the opening size. **Release and install.** `release.yml` builds `gpui-shell` for aarch64/x86_64 macOS, x86_64 Linux and x86_64 Windows. Asset names carry no version, e.g. `gpui-shell-aarch64-apple-darwin.tar.gz` plus `.sha256`, so `releases/latest/download/<asset>` always resolves. `curl -fsSL https://gpui-kit.com/shell/install.sh | sh` installs it to `~/.local/bin`; the script source is `website/public/shell/install.sh`. **Catalog.** - New `TitleBar` binding. - Catalog windows use `TitleBar::window_options()`, so Sheets and notifications line up with the title bar. - `js_story`, `js_todolist` and `js_dock` now render a `TitleBar`. **Fixes.** - `localStorage.getItem` / `key` return `null` rather than `undefined` for a missing entry. This is what made `js_todolist` warn "storage unavailable". - OtpInput repaints on every keystroke. Its state was a plain entity, so its notify dirtied no view, and the cached content replayed until the pointer moved. - Importing `gpui-component` without a catalog now explains how to get one, instead of listing it as a built-in module. - The load-failure page's copy action is an icon button (copy, then check), so it can no longer overflow the panel. **Docs.** - Getting Started, en + zh-CN: installing, `run` / `new` / `list` / `agents`, and installed and packaged applications. - New Packaging page, en + zh-CN: macOS `.app` with `Info.plist`, `lipo`, signing and notarization; a Linux archive and `.desktop` entry; a Windows folder. ## Public API ### gpui-shell - Feature `components` (default): the `gpui-component` catalog. Optional dependency on `gpui-component`. - `pub mod components` (with `components`): - `pub fn init(cx: &mut App)`: initializes `gpui-component` and the runtime (was `gpui_component_shell::init`). - `pub fn components() -> Result<FrozenComponentRegistry, RegistryError>`: the frozen catalog (was `gpui_component_shell::components`). - `pub fn new_isolated_runtime() -> anyhow::Result<Rc<ShellRuntime>>`: an isolated runtime with the catalog (was `gpui_component_shell::new_isolated_runtime`). - `pub struct plugin::WindowSize` with `width(&self) -> f32` and `height(&self) -> f32`: a manifest's opening window size. - `PluginManifest::window(&self) -> Option<WindowSize>`: the manifest's `window`, if any. - `ManifestProblem::Window(String)`: an invalid `window` field. - `host::InvocationKind::{List, Agents, New}`: classifications for the new commands. - Manifest field `window: { width: number, height: number }`: logical pixels, positive, no other keys. - JavaScript: `window.start_window_move(): void` (GPUI `Window::start_window_move`), so a script-drawn title bar can drag the window. - JavaScript, `"gpui-component"`: `TitleBar` (`new TitleBar()`, with styles, `child`/`children` and `on_close_window(callback: (cx) => void)`). It is declared in the generated `gpui-kit.d.ts`. ## Validation - `cargo test -p gpui-shell --no-fail-fast`: 34 test binaries, 990 tests, no failures. - `cargo clippy -p gpui-shell --all-targets -- -D warnings`, `cargo fmt --check -p gpui-shell`, `cargo machete`, `typos`: clean. - `cargo check -p gpui-shell --no-default-features --features quickjs`: the runtime builds without the catalog. - `gpui-shell check -d` passes for `examples/js_todolist`, `examples/js_dock` and `examples/js_story`. - `npm --prefix crates/shell/tests/types test` and `node examples/js_story/fixtures/verify-coverage.mjs` pass. - Manually: `new` → `check` on a scaffolded application; a hand-built `Demo.app/Contents/{MacOS,Resources/app}` starts its bundled application with no arguments; OtpInput repainting on every keystroke was confirmed by eye in the running gallery. Not verified: - The release job and the install script have not run against a real release. - Bringing the window forward could not be confirmed from a background shell; macOS will not let it take focus from another app. It needs a check from a terminal launch. - Cmd-Q / Alt-F4 was not pressed in testing. - The manifest `window` size was unit-tested only, not opened end to end. - The TitleBar in `js_story`, `js_todolist` and `js_dock`, and whether a Sheet now opens flush under it, has not been looked at in a running window. ## AI-generated This change was written with Claude Code (one main session, two sub-agents for TitleBar and OtpInput, and a separate session for the manifest `window` field), reviewed and directed by the author. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> | 2 天前 |
| 文件 | 最后提交记录 | 最后更新时间 |
|---|---|---|
| 22 天前 | ||
| 2 天前 |