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.

referencesarchitecture-state.md

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

Architecture and state reference

Stack boundary: follow the framework choice. For Kit apps, read the relevant current Kit guide first and import through gpui_kit. The direct gpui/gpui_platform code and pinned-source claims below belong to the retained upstream fixture; they do not override Kit components, tokens, Root/overlay ownership, motion, or tests. Preserve the product and platform contracts here, checking exact APIs in the chosen stack before use.

Use this layer to decide what owns state, how views communicate, when to notify, and how tasks and subscriptions follow entity lifetime.

Contents

Mental model

GPUI combines retained application state with declaratively rebuilt element trees:

  • App owns application-wide facilities, entities, globals, executors, windows, and registrations.
  • Entity<T> is a cloneable handle to application-owned mutable T.
  • Context<T> mutates the currently owned entity and connects it to the app.
  • Render rebuilds a view's element tree from current state.
  • RenderOnce consumes a value-like component to produce an element.
  • Window supplies window-local input, focus, layout, and frame facilities.
  • WeakEntity<T> observes ownership without keeping an entity alive.

The element tree is not the state model. Render it from durable state and let GPUI reconcile layout, paint, and interaction.

Choose state ownership

Use the narrowest owner that preserves truth:

State Good owner
Hover/pressed state expressible by element styling Element state
Open/closed, selection, draft text, scroll relationship Owning view entity
Shared documents, sessions, workspaces, or caches Model entity
Process-wide service or immutable configuration App global/service
Derived text, enabled state, visibility Compute from source state
Temporary async result Owning entity plus generation/cancellation state

Keep one writer or explicit command boundary for each value. Do not duplicate “selected item” in a list row, parent view, and model unless synchronization is formal and tested.

Create an Entity<T> when the value:

  • changes independently;
  • is observed by multiple owners;
  • survives element rebuilds;
  • owns tasks/subscriptions;
  • emits domain events;
  • needs focused GPUI tests.

Use RenderOnce when a component is configured by value, has no independent lifetime, and delegates behavior through callbacks/actions. Avoid entities for decorative wrappers.

Use contexts deliberately

Current GPUI exposes related context types with different capabilities. Exact methods vary by pinned version.

Context Typical work
App globals, registration, entities, executors, windows
Context<T> mutate T, notify, emit, spawn, observe, subscribe
Window focus, input, window state, frame/layout interaction
AsyncApp access app state from async application-thread work
AsyncWindowContext async work that also needs a specific window

Do not smuggle a mutable app/window reference into background work. Move owned data across the boundary, compute, then update through the supported async context.

Use the weak handle supplied by current Context::spawn in long-running work:

let task = cx.spawn(async move |view, cx| {
    let result = load_data().await;
    view.update(cx, |view, cx| {
        view.finish_loading(result);
        cx.notify();
    })?;
    anyhow::Ok(())
});

This is the current shape, not a guaranteed signature for every revision. Copy a compiling local sibling and propagate errors according to project policy.

Model actions and events

Use typed actions for user intent that participates in:

  • keyboard bindings;
  • menus and command palettes;
  • reusable input routing;
  • focus-scoped behavior;
  • testable application commands.

Use key contexts to scope bindings. A generic Enter or Escape handler should not fire everywhere because a descendant forgot to stop propagation.

Use emitted events for child-to-owner domain communication:

  • selection changed;
  • submitted;
  • dismissed;
  • requested deletion;
  • navigation requested.

Prefer semantic intent over pointer mechanics. Emit Submit, not MouseWasReleasedAt(x, y).

Use direct callbacks when the relationship is local and value-like. Use actions when behavior should be remappable or routed. Use events when an entity exposes a stable outward contract.

Document propagation policy for nested shortcuts:

  1. Which focus/key context receives the action?
  2. When does the handler stop propagation?
  3. What is the parent fallback?
  4. What happens while a modal or menu is open?

Update and notify correctly

Mutating state does not help if observers or rendering never learn about it.

  • Call cx.notify() after a mutation changes this entity's rendered output.
  • Emit a typed event when another owner must respond.
  • Let observed entities trigger dependent updates through an owned observation.
  • Avoid redundant notifications inside tight loops; batch related mutation.
  • Do not notify after calculating an identical value unless downstream behavior relies on it and that contract is explicit.

Keep a transition centralized:

fn select(&mut self, id: ItemId, cx: &mut Context<Self>) {
    if self.selected == Some(id) {
        return;
    }

    self.selected = Some(id);
    cx.emit(SelectionChanged(id));
    cx.notify();
}

When a callback updates another entity, make the ownership path obvious. Deeply nested update calls are a sign that domain operations may belong on a shared model.

Own tasks and subscriptions

Current GPUI lifecycle behavior is important:

  • a returned Task must be held or detached;
  • dropping a held task cancels its work;
  • detaching deliberately lets it continue;
  • a held Subscription is cancelled when dropped;
  • detached subscriptions follow entity-lifetime semantics supported by GPUI.

Store lifecycle-bound work:

struct SearchView {
    query: SharedString,
    search_task: Option<Task<()>>,
    subscriptions: Vec<Subscription>,
}

Replace a held task to cancel stale work. For requests that cannot be cancelled reliably, add a monotonically increasing generation and discard results from an older generation.

Detach only for deliberate app-lifetime work such as telemetry flushing or a background service. Observe failures; .detach() must not become shorthand for “ignore the result.”

Use cx.observe for entity invalidation/dependency relationships and cx.subscribe for typed emitted events. Hold the returned subscription in the owner whose lifetime defines the relationship.

Represent product states

Avoid parallel booleans such as is_loading, has_error, and has_data that can create impossible combinations. Prefer an enum:

enum LoadState<T> {
    Idle,
    Loading { generation: u64 },
    Ready(T),
    Empty,
    Failed { message: SharedString, retryable: bool },
}

Represent interaction state explicitly when it changes behavior:

  • disabled and why;
  • pending operation and cancellation;
  • selection;
  • open overlay and focus return target;
  • validation errors;
  • drag phase and active pointer/touch;
  • animation target and velocity.

Derive visuals from the state machine. Do not use an animation completion callback as the only source of product truth.

Errors should be:

  • actionable to the user when recovery is possible;
  • recorded for developers with relevant context;
  • preserved across rerenders;
  • cleared by a defined event;
  • testable.

Set module boundaries

Organize by ownership, not by one file per type:

  • domain model: operations, invariants, persistence boundary;
  • view entity: orchestration, focus, actions, state mapping;
  • components: reusable appearance and local interaction contract;
  • theme: semantic roles, platform/material policy;
  • platform bridge: availability checks and native ownership;
  • pure modules: parsing, reducers, geometry, springs;
  • tests close to the behavior plus integration coverage where needed.

Keep public constructors small. Prefer a builder only when optional configuration is numerous and callers benefit. Validate invariants once rather than sprinkling defensive checks through render code.

Review smells

  • Render-local variables pretend to be durable state.
  • Multiple entities own unsynchronized copies of one domain value.
  • A mutation changes output without cx.notify().
  • A task is created and immediately dropped.
  • Every task is detached, including stale queries.
  • Subscriptions are accumulated after every render.
  • Pointer callbacks bypass typed actions and keyboard access.
  • A child knows the entire parent implementation.
  • App globals are used for screen-local state.
  • Errors become unwrap() or disappear inside detached work.
  • A model performs window/input work.
  • A visual refactor silently changes domain ownership.

For async details, continue with async-performance.md. For complete patterns, continue with worked-patterns.md.

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