Files
gpui-native-kit/AGENTS.md
T
2026-10-28 17:02:02 +07:00

8.7 KiB

Coding guidelines

  • Prioritize code correctness and clarity. Speed and efficiency are secondary priorities unless otherwise specified.
  • Do not write organizational or comments that summarize the code. Comments should only be written to explain "why" the code is written in some way in the case there is a reason that is tricky / non-obvious.
  • Keep doc comments and comments simple, one line, straightforward, and unbloated.
  • Prefer implementing functionality in existing files unless it is a new logical component. Avoid creating many small files.
  • Prefer impl blocks over standalone functions: place functionality as methods or associated functions on the relevant type, extending the type's existing impl block instead of creating a duplicate one in another file.
  • Always use generic parameters with a where clause instead of argument-position impl Trait in function signatures (fn extend<I>(&mut self, elements: I) where I: IntoIterator<Item = AnyElement>); return-position impl Trait stays.
  • Do not wrap dependency APIs in project structs or pass-through helper functions; call their methods directly where needed. Extract a helper only when it holds real logic that is used in more than one place.
  • Bind a global store handle to a local before using it (let theme = Theme::global(cx); then theme.…) instead of chaining ::global(cx) inline.
  • Avoid using functions that panic like unwrap(); instead use mechanisms like ? to propagate errors.
  • Be careful with operations like indexing which may panic if the indexes are out of bounds.
  • Never silently discard errors with let _ = on fallible operations. Always handle errors appropriately:
    • Propagate errors with ? when the calling function should handle them.
    • Use .log_err() or similar when you need to ignore errors but want visibility.
    • Use explicit error handling with match or if let Err(...) when you need custom logic.
  • When implementing async operations that may fail, ensure errors propagate to the UI layer so users get meaningful feedback.
  • Avoid creative additions unless explicitly requested.
  • Use full words for variable names (no abbreviations like "q" for "queue").
  • Use variable shadowing to scope clones in async contexts for clarity, minimizing the lifetime of borrowed references.

Tests

  • Keep only meaningful and critical tests: each test must lock state semantics (activation, state transitions, style priority, a11y mapping) or a non-obvious lifecycle invariant.
  • Drop tests that assert mechanical passthroughs, getter/setter round-trips, compile-time API availability, or duplicate the same behavior at a second layer.

Timers in tests

  • In GPUI tests, prefer GPUI executor timers over smol::Timer::after(...) for timeouts, delays, or driving run_until_parked():
    • Use cx.background_executor().timer(duration).await (or cx.background_executor.timer(duration).await in TestAppContext) so work is scheduled on GPUI's dispatcher.
    • Avoid smol::Timer::after(...) for test timeouts when you rely on run_until_parked(); it may not be tracked by GPUI's scheduler.

GPUI

GPUI is a UI framework which also provides primitives for state and concurrency management.

Context

  • Context types allow interaction with global state, windows, entities, and system services. They are typically passed as the argument named cx. When a function takes callbacks they come after the cx parameter.
  • App is the root context type, providing access to global state and read and update of entities.
  • Context<T> is provided when updating an Entity<T>. It dereferences into App, so functions which take &App can also take &Context<T>.
  • AsyncApp and AsyncWindowContext are provided by cx.spawn and cx.spawn_in and can be held across await points.

Window

  • Window provides access to the state of an application window. It is passed as window and comes before cx when present.

Entities

  • With thing: Entity<T>: thing.entity_id(), thing.downgrade(), thing.read(cx), thing.read_with(cx, |thing, cx| ...), thing.update(cx, |thing, cx| ...), thing.update_in(cx, |thing, window, cx| ...).
  • Within update closures, use the inner cx provided to the closure, not the outer one.
  • Never update an entity while it is already being updated; this panics.
  • WeakEntity<T> methods return anyhow::Result and fail when the entity no longer exists; treat that as a normal lifecycle outcome. Prefer weak back-references to avoid ownership cycles.

Concurrency

  • All use of entities and UI rendering occurs on a single foreground thread.
  • cx.spawn(async move |cx| ...) runs on the foreground thread; from a Context<T> it looks like cx.spawn(async move |this, cx| ...).
  • cx.background_spawn(async move { ... }) runs work on other threads.
  • Both return a Task<R>; dropping it cancels the work. Prevent cancellation by awaiting, detaching deliberately, or storing the task in a field.
  • Prefer keeping a task in a field over .detach() when the work belongs to a view or store; a detached task outlives its owner. A finished Task held in a container is not reaped — drop finished handles or hold a single Option<Task<_>>.

Rendering and state

  • Render builds an element tree from entity state; RenderOnce (with #[derive(IntoElement)]) is for value-like components whose render takes self and &mut App.
  • Keep render declarative; use .when(...) / .when_some(...) for small conditional refinements.
  • Controlled components: the owner passes the current value, callbacks report intent, the owner updates state and calls cx.notify(). No second source of truth.
  • Call cx.notify() after a mutation that changes rendering; notify the narrowest owning entity. Never notify from render.
  • Use cx.emit(...) for semantic events (declare impl EventEmitter<EventType>), cx.subscribe(...) / cx.observe(...) for reactions, and store returned Subscriptions in the owner (commonly _subscriptions: Vec<Subscription>).
  • Use stable domain-derived ElementIds (("delete-project", project.id)); never list indexes or freshly generated random IDs for reorderable content.
  • cx.listener(...) adapts event handlers to update the current entity.
  • Actions: actions!(namespace, [Action]) for data-free actions, the Action derive otherwise; register on_action on the focused region with a matching key_context.
  • Retain a FocusHandle in the entity that owns keyboard interaction; a tracked handle is a Tab stop only when built with tab_stop(true).

Project rules

  • Dependency direction is one-way: gpui-native-kit (facade) → gpui-native-core / gpui-native-editor / … → gpui-native-theme + gpui-native-icons → gpui. No cycles. Nothing depends on gpui-component, gpui-kit, gpuikit, bezel, native-theme, or any gpui fork.
  • crates/theme owns the native theme engine and every color, radius, spacing, and metric value — no raw rgb/rgba/hsla or layout px(...) literals anywhere else.
  • crates/icons owns icon resolution; components reference icons by semantic role — no glyph names, unicode symbols, or SVG paths anywhere else.
  • crates/core and its sibling crates own components: each one is a small self-contained implementation gated behind its own cargo feature. Features are additive, gate whole modules only, and may depend on other features. Behavior and presentation live together. No separate behavior layer — promote a shared helper only when a third component needs it.
  • Components are written in-tree. Other UI toolkits are read for API ideas only; never port their code or depend on them.
  • gpui/gpui_platform come from zed's repository at the rev pinned in docs/PLAN.md; bump the pin deliberately and re-verify.
  • SharedString for UI text retained across frames; String only for mutable buffers.
  • Behavioral seams use private fields + builders/readers. Boolean readers are is_/has_; general builders use with_; in-place mutation uses set_; callbacks use on_; retained models end in State; pluggable owners in Delegate/Provider.
  • Never invent an API: search the gpui source for the real signature before writing code.
  • Prefer .SystemUIFont and rem-based helpers (p_2(), text_sm(), gap_3()) over pixel literals.

Pull request hygiene

  • Use a clear, correctly capitalized, imperative PR title (for example, fix crash in project panel).

  • Avoid conventional commit prefixes in PR titles (fix:, feat:, docs:, etc.).

  • Avoid trailing punctuation in PR titles.

  • Optionally prefix the title with a crate name when one crate is the clear scope (for example, kit: add button port unit).

  • Include a Release Notes: section as the final section in the PR body, with one bullet:

    • - Added ..., - Fixed ..., or - Improved ... for user-facing changes, or - N/A otherwise.

    Release Notes:

    • N/A