All skills
lassejlv avatar

/build-gpui-apps

@52f4cfd
by Lasselassejlv/skills50 stars
1

Build, scaffold, refactor, debug, review, and validate native Rust desktop applications with GPUI. Recommend GPUI Kit and ask before adopting it; use gpui_kit imports after agreement, or preserve the chosen upstream GPUI stack. Includes the merged GPUI Kit component and design skills, full application/Base/Component/Shell documentation, coding and design guides, state, actions, async, input, accessibility, motion, themes, native integration, packaging, auto updates, testing, and production delivery. Use paper-to-gpui when the primary task is faithfully translating a selected Paper.design frame into an existing view.

Use this Skill: https://skilld.dev/gh/lassejlv/skills/build-gpui-apps

This session only. Nothing lands on disk.

referencesgpui-kitgpuiasync.md

≈1.6k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Async & Background Tasks

Contents: Overview · Quick Start · Core Patterns · Common Pitfalls

Overview

GPUI provides integrated async runtime for foreground UI updates and background computation.

Key Concepts:

  • Foreground tasks: UI thread, can update entities (cx.spawn)
  • Background tasks: Worker threads, CPU-intensive work (cx.background_spawn)
  • All entity updates happen on foreground thread

Quick Start

Foreground Tasks (UI Updates)

When spawned from Context<Self>, the closure receives (WeakEntity<Self>, &mut AsyncApp):

impl MyComponent {
    fn fetch_data(&mut self, cx: &mut Context<Self>) {
        cx.spawn(async move |this, cx: &mut AsyncApp| {
            // Runs on UI thread, can await and update entities
            let data = fetch_from_api().await;

            this.update(cx, |state, cx| {
                state.data = Some(data);
                cx.notify();
            }).ok();
        }).detach();
    }
}

When spawned from &mut App (not inside an entity), the closure receives only (cx: &mut AsyncApp):

cx.spawn(async move |cx: &mut AsyncApp| {
    // No entity reference
}).detach();

Spawn with Window Context (spawn_in)

Use spawn_in when the task also needs window access (update_in):

impl MyComponent {
    fn animate(&mut self, window: &mut Window, cx: &mut Context<Self>) {
        cx.spawn_in(window, async move |this, cx| {
            // cx here is AsyncWindowContext
            this.update_in(cx, |state, window, cx| {
                // Can access window here
                state.frame += 1;
                cx.notify();
            }).ok();
        }).detach();
    }
}

Background Tasks (Heavy Work)

impl MyComponent {
    fn process_file(&mut self, cx: &mut Context<Self>) {
        let entity = cx.entity().downgrade();

        cx.background_spawn(async move {
            // Runs on background thread, CPU-intensive
            let result = heavy_computation().await;
            result
        })
        .then(cx.spawn(move |result, cx| {
            // Back to foreground to update UI
            entity.update(cx, |state, cx| {
                state.result = result;
                cx.notify();
            }).ok();
        }))
        .detach();
    }
}

Task Management

struct MyView {
    _task: Task<()>,  // Prefix with _ if stored but not accessed
}

impl MyView {
    fn new(cx: &mut Context<Self>) -> Self {
        let _task = cx.spawn(async move |this, cx: &mut AsyncApp| {
            // Task automatically cancelled when dropped
            loop {
                cx.background_executor().timer(Duration::from_secs(1)).await;

                this.update(cx, |state, cx| {
                    state.tick();
                    cx.notify();
                }).ok();
            }
        });

        Self { _task }
    }
}

Core Patterns

1. Async Data Fetching (from Context<Self>)

cx.spawn(async move |this, cx: &mut AsyncApp| {
    let data = fetch_data().await?;
    this.update(cx, |state, cx| {
        state.data = Some(data);
        cx.notify();
    })?;
    Ok::<_, anyhow::Error>(())
}).detach();

2. Background Computation + UI Update

cx.background_spawn(async move {
    heavy_work()
})
.then(cx.spawn(move |this, cx: &mut AsyncApp| {
    this.update(cx, |state, cx| {
        state.result = result;
        cx.notify();
    }).ok();
}))
.detach();

3. Periodic Tasks

cx.spawn(async move |this, cx: &mut AsyncApp| {
    loop {
        cx.background_executor().timer(Duration::from_secs(5)).await;

        this.update(cx, |state, cx| {
            state.tick();
            cx.notify();
        }).ok();
    }
}).detach();

4. Task Cancellation

Tasks are automatically cancelled when dropped. Store in struct to keep alive.

Common Pitfalls

❌ Don't: Use defer_in and then update the same entity through its handle

cx.defer_in(window, callback) schedules callback to run on the current entity — GPUI re-acquires that entity's lock to execute it. Calling entity.update(cx, …) on the same entity from within the deferred callback re-enters the lock and panics:

cannot update … while it is already being updated
// ❌ Panic: list entity is locked for the defer_in; calling list.update re-enters
fn confirm(&mut self, _: bool, window: &mut Window, cx: &mut Context<ListState<Self>>) {
    cx.defer_in(window, |list_state, window, cx| {
        parent.update(cx, |this, cx| {
            this.inner_list.update(cx, |_, _| {}); // PANIC if inner_list == the deferred entity
        });
    });
}
// ✅ Correct: use the direct &mut reference — no lock needed
fn confirm(&mut self, _: bool, window: &mut Window, cx: &mut Context<ListState<Self>>) {
    cx.defer_in(window, |list_state, window, cx| {
        // Access list data directly through the &mut reference
        list_state.delegate_mut().some_method();

        // Update a *different* entity — fine, different lock
        parent.update(cx, |this, cx| { /* … */ });

        // Sync list state directly after parent update — no lock needed
        list_state.delegate_mut().update_snapshot(new_val);
    });
}

The rule: inside a defer_in callback, never call entity.update(cx, …) or entity.read(cx) on the entity the defer_in was scheduled on. Use the &mut Entity direct reference the callback provides instead.

❌ Don't: Update entities from background tasks

// ❌ Wrong: Can't update entities from background thread
cx.background_spawn(async move {
    entity.update(cx, |state, cx| { // Compile error!
        state.data = data;
    });
});

✅ Do: Use foreground task or chain

// ✅ Correct: Chain with foreground task
cx.background_spawn(async move { data })
    .then(cx.spawn(move |data, cx| {
        entity.update(cx, |state, cx| {
            state.data = data;
            cx.notify();
        }).ok();
    }))
    .detach();

Merged from GPUI Kit skill. Apache-2.0. Imported 2026-09-28; local links and version-sensitive guidance adapted. The current documentation and locked source take precedence over example signatures.

Source: SKILL.md on GitHub

No alerts3d3 checks · Risk SAFE
  • Gen Agent Trust Hub3d

    The skill provides comprehensive documentation and reference material for building native desktop applications using Rust and GPUI. No security risks, prompt injections, or malicious behaviors were identified. External dependencies originate from reputable organizations in the Rust ecosystem.

  • Socket3d

    No alerts

  • Snyk3d

    Risk: LOW · No issues

Signed by skilld at 52f4cfd. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub yesterday.

Activeupdated 4 days ago

README badge

README badge for lassejlv/skills/build-gpui-apps