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
modelThe domain types.
storeLocal persistence.
hostThe async facade. Everything slow lives here.
appThe UI, and the one bridge call per dispatch.
Start it
scripts/new.sh Acme ~/Dev/acme --template minimal
What it looks like
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());
});
}
}