Desktop field Plate 16 Route §16
Plate 16

The single-window tool that waits

Still one window, but now something slow sits behind it — and gpui's main thread is not where slow things go.

Add a host crate: the async service facade, gpui-free, where every operation the app can perform is a method. Views never touch it directly. They dispatch through atlas::bridge::run, which puts the future on the process-wide tokio runtime and delivers the result back on the UI thread.

One seam is the whole point. There is one place to look when a result never arrives, and one runtime to reason about rather than a Runtime::new() per module and four thread pools fighting over the same cores.

Use Load<T> for what comes back. An Option<Vec<T>> cannot tell "still loading" from "loaded and empty" from "failed", which is how an empty table ends up spinning forever.

What you are carrying

model

The domain types.

store

Local persistence.

host

The async facade. Everything slow lives here.

app

The UI, and the one bridge call per dispatch.

Start it

scripts/new.sh Acme ~/Dev/acme --template minimal

What it looks like

crates/app/src/state.rsrust
use atlas::prelude::*;

impl AppState {
    /// Fetch the list and publish it. Three states in one signal, so a view
    /// cannot render a failure as an empty list.
    pub fn reload(&self, cx: &mut gpui::App) {
        let host = Arc::clone(&self.host);
        let items = self.items.clone();
        items.set(cx, Load::Loading);

        bridge::run(cx, async move { host.items().await }, move |result, cx| {
            items.set(cx, result.into());
        });
    }
}

Read next

Desktop architecture Gotchas
Type to search guides and package references.