Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/GraphiteEditor/Graphite/llms.txt

Use this file to discover all available pages before exploring further.

The Graphite editor is the application users interact with to create and modify documents. Its code is a single Rust crate that sits between the Svelte frontend (web code) and the Graphene engine (the node-based graphics engine). All of the main business logic for visual editing lives here. When running in the browser, this crate is compiled to WebAssembly and exchanges messages with the frontend; the same crate is also used by the native desktop build.

Message System

The Graphite editor backend is organized into a hierarchy of subsystems that communicate through message passing. Messages are pushed to the front or back of a queue and each one is processed sequentially by the editor’s dispatcher. The dispatcher lives at the root of the editor hierarchy and owns all top-level message handlers. This design satisfies Rust’s restrictions on mutable borrows: only the dispatcher may mutate its message handlers, one at a time, while each message is being processed. There is no shared mutable state across handlers — the dispatcher is the sole coordinator.

Three Subsystem Components

Every subsystem in the editor is made up of three cooperating parts:

*Message enum

Defines the interface of a subsystem as Rust enum variants. Each variant represents a request to perform some action, optionally carrying data fields. For example, DocumentMessage::DeleteLayer { id: NodeId } is a message that carries a node ID as a named field.

*MessageHandler struct

Owns the persistent state for a subsystem and contains the logic for handling each message variant it receives from the dispatcher. Child message handlers for nested subsystems are stored as fields of this struct. Handler logic may enqueue further messages during the same dispatch cycle.

*MessageContext struct

Defines the read-only context passed to a handler when processing a message — borrowed state made available from the parent handler. Intermediate layers forward context from parent to child, making higher-level state accessible further down the hierarchy.

Message Hierarchy

Messages nest hierarchically using the #[child] attribute on enum variants. A #[child]-annotated variant wraps a child message enum as its payload. The dispatcher always operates on the top-level Message type, so sub-messages must be wrapped in their ancestor variants. Here is how DocumentMessage is defined as a child of PortfolioMessage:
pub enum PortfolioMessage {
    // ...
    // A message that carries the `DocumentMessage` child enum as data
    #[child]
    Document(DocumentMessage),
    // ...
}
And PortfolioMessage is itself wrapped by the top-level Message enum. So internally, dispatching DeleteSelectedLayers produces a fully nested value:
Message::Portfolio(
    PortfolioMessage::Document(
        DocumentMessage::DeleteSelectedLayers
    )
)
Writing that out manually everywhere would be cumbersome. The #[child] attribute invokes a proc macro that auto-implements the From trait, so the entire chain can be written as:
DocumentMessage::DeleteSelectedLayers.into()
And because .add() and .add_front() call .into() for you, the final form used throughout the codebase is simply:
responses.add(DocumentMessage::DeleteSelectedLayers);
The #[child] proc macro auto-implements the From trait for every level of the nesting chain. This means any descendant message type can be passed directly to responses.add() without manually wrapping it in its ancestor variants.

Key Top-Level Subsystems

The editor’s message system is organized into these top-level subsystems, each with its own *Message, *MessageHandler, and *MessageContext:
SubsystemPurpose
PortfolioManages the set of open documents, the active document, and clipboard
DocumentOwns a single document’s state: layers, history, viewport
ToolActive tool state, tool options, and tool interaction events
InputRaw keyboard and mouse input preprocessing
LayoutUI layout definitions sent to the frontend
DialogModal dialogs and confirmation prompts
AnimationAnimation playback and timeline management
NetworkThe node graph network for the current document
PreferencesUser preferences and editor settings
Additional subsystems handle concerns such as debug utilities, clipboard, broadcast events, and viewport management.

Exploring the Hierarchy

The editor ships a built-in exploration tool that opens an interactive, searchable outline of the subsystem hierarchy in your browser:
cargo run explore editor
This command opens the editor structure page on graphite.rs in your default browser, displaying a structured outline of every message handler, its children, and its messages. It is the fastest way to navigate the structure of the editor when onboarding or looking for where a particular action is handled.

Adding a New Message

1

Define the variant

Add a new variant to the appropriate *Message enum, with named struct-style fields for any data the handler needs.
2

Implement the handler branch

Add a match arm inside the process_message method of the corresponding *MessageHandler. Enqueue follow-up messages as needed using responses.add().
3

Dispatch from a call site

Call responses.add(YourMessage::NewVariant { field: value }) from any handler. The From impls generated by #[child] automatically wrap it into a top-level Message.
For further context, see Graphene Engine for how the editor interacts with the node graph, and Frontend & Backend for how messages cross the Wasm boundary.

Build docs developers (and LLMs) love