Skip to content

root: Add Base window hosting and a single Kit startup entry point - #3152

Merged
huacnlee merged 16 commits into
mainfrom
root-auto-layers
Sep 22, 2026
Merged

huacnlee merged 16 commits into
mainfrom
root-auto-layers

Conversation

@huacnlee

@huacnlee huacnlee commented Sep 21, 2026 •

Copy link
Copy Markdown
Member

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

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:

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

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:

- 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:

- 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:

- Root::bordered
- Root::window_shadow_size
+ window_border()

The Root- and WindowExt-owned text-selection methods are removed in favor of gpui_base::TextSelection:

- 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:

- 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.

@huacnlee huacnlee changed the title root: Render the overlay layers from Root, and add open_window root: Render the overlay layers from Root, add open_window and a default quit key Sep 21, 2026
@huacnlee huacnlee changed the title root: Render the overlay layers from Root, add open_window and a default quit key root: Render the overlay layers from Root, add open_window and default window keys Sep 21, 2026
…fault window keys

A window's dialogs, sheets and notifications live on its `Root`, but
`Root` only stored them: the application's first view had to call
`Root::render_dialog_layer` and its two siblings, or `open_dialog` did
nothing at all. Every downstream application learned this by watching a
dialog not appear, wrote it into its own guide and grew a wrapper view
for it. Getting a window up also took three nested closures around
`Root::new` that told a newcomer nothing about why, and every example
bound the platform's quit shortcut by hand.

`Root` now places the layers itself, after the view. A `RootLayers`
element laid out as the view's next sibling reads which layers the view
already rendered this frame — the public `render_*_layer` functions
record that — and renders the rest, so an application that positions a
layer under its own title bar keeps it there and gets no second copy.

`gpui_kit::open_window(options, cx, build)` is `cx.open_window` with the
window made ready for the kit: a `Root` wraps the view when the
`component` feature is on, the view is the root otherwise, and both the
window and the view come back. `gpui_base::init` binds `cmd-q` on macOS
and `alt-f4` elsewhere to a `Quit` action, and `cmd-w` on macOS to
`CloseWindow`; a later binding to the app's own action still wins. The
examples, recipes and docs move to all of this, and the two panics for a
window without a `Root` now say what to do.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Remove manual layer APIs and default lifecycle shortcuts, migrate consumers, and document the pending 0.7.0 changes.

Co-authored-by: Codex <codex@openai.com>
@huacnlee huacnlee changed the title root: Render the overlay layers from Root, add open_window and default window keys root: Mount overlay layers automatically and add open_window Sep 21, 2026
huacnlee and others added 2 commits September 21, 2026 18:44
@huacnlee huacnlee changed the title root: Mount overlay layers automatically and add open_window root: Move window hosting to Base and mount component layers automatically Sep 21, 2026
@huacnlee huacnlee changed the title root: Move window hosting to Base and mount component layers automatically root: Add Base window hosting and a single Kit startup entry point Sep 21, 2026
huacnlee and others added 2 commits September 21, 2026 19:29
Add version switching with latest release at the site root, keep main under /versions/main, and cache historical release builds.

Co-authored-by: Codex <codex@openai.com>
@huacnlee huacnlee closed this Sep 21, 2026
@huacnlee huacnlee added this to the 0.7.0 milestone Sep 22, 2026
@huacnlee huacnlee reopened this Sep 22, 2026
huacnlee and others added 9 commits September 22, 2026 11:25
Co-authored-by: Codex <codex@openai.com>
Co-authored-by: Codex <codex@openai.com>
Co-authored-by: Codex <codex@openai.com>
Co-authored-by: Codex <codex@openai.com>
Co-authored-by: Codex <codex@openai.com>
Co-authored-by: Codex <codex@openai.com>
Co-authored-by: Codex <codex@openai.com>
Co-authored-by: Codex <codex@openai.com>
Co-authored-by: Codex <codex@openai.com>
@huacnlee
huacnlee merged commit 5bc71a4 into main Sep 22, 2026
12 checks passed
@huacnlee
huacnlee deleted the root-auto-layers branch September 22, 2026 07:36
dwander added a commit to dwander/gpui-component that referenced this pull request Sep 22, 2026
build/chore: 업스트림 GPUI Kit 병합 — 충돌 해결 + 중복된 포크 패치 걷어내기

- Cargo.toml: 상류가 gpui 계열을 crates.io `gpui-pre-*` 핀으로 되돌리는 것을 매번처럼
  zed git 참조로 복구(안 하면 gpui 가 두 벌 링크된다). sum-tree·reqwest 는 상류 범프를 따른다.
- styled.rs: 상류가 링 그리기를 `focus_ring()` 자유 함수로 뽑았다. 우리 `focus_ring_style_always`
  를 그 위에 다시 얹어 중복 본문 60줄을 걷어냈다.
- dialog/alert_dialog: 상류 longbridge#3126 이 `button_props` 를 덮어쓰기가 아니라 병합으로 바꿔,
  on_close 유실을 막으려던 우리 패치(AlertDialog 전용 button_props + DialogButtonProps::on_close
  빌더)가 불필요해졌다 — 걷어내고 상류 구조를 쓴다. overlay_dim 은 그대로 유지.
- root.rs/lib.rs: 상류 longbridge#3152 로 `Root` 가 Base 소유가 되고 `WindowState` 는 크레이트 내부
  타입이 됐다. 앱에서 쓰던 툴팁 억제를 `suppress_tooltip`/`restore_tooltip` 자유 함수로 노출.
- popover.rs: 병합이 만든 중복 import 정리.
huacnlee pushed a commit that referenced this pull request Sep 28, 2026
## Description

The README examples use `gpui_kit::open_window`, which was added in
0.7.0 (#3152). The install snippets next to them still say `gpui-kit =
"0.6"`.

For 0.x crates, Cargo treats `"0.6"` as `>=0.6.0, <0.7.0`, so anyone who
copies the README gets 0.6.6. The example then fails to compile:

```
error[E0425]: cannot find function `open_window` in crate `gpui_kit`
```

This PR changes every hardcoded `gpui-kit` requirement in Markdown from
`0.6` to `0.7`:

- `README.md`
- `README.zh-CN.md`
- `crates/kit/README.md` (the crates.io readme; the published page
updates with the next release)
- `crates/base/README.md`
- `examples/app_assets/README.md`
- `skills/gpui-kit/references/usage.md`

The website docs are not affected because they use
`{{gpui_kit_version}}`.

`script/bump-version.sh` only runs `cargo set-version`, which does not
touch Markdown. These snippets will need a manual update again on the
next minor release.

## How to Test

- `git grep -nE 'gpui-kit = (\{ *version = )?"[0-9]' -- '*.md'` lists
only `0.7`.
- A new project with `gpui-kit = "0.7"` and the README example resolves
to 0.7.0 and calls `gpui_kit::open_window`.

## Checklist

- [ ] I have read the [CONTRIBUTING](../CONTRIBUTING.md) document and
followed the guidelines.
- [ ] Reviewed the changes in this PR and confirmed AI generated code
(If any) is accurate.
- [ ] Passed `cargo run` for story tests related to the changes.
- [ ] Tested macOS, Windows and Linux platforms performance (if the
change is platform-specific)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant