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.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.
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:
PortfolioMessage is itself wrapped by the top-level Message enum. So internally, dispatching DeleteSelectedLayers produces a fully nested value:
#[child] attribute invokes a proc macro that auto-implements the From trait, so the entire chain can be written as:
.add() and .add_front() call .into() for you, the final form used throughout the codebase is simply:
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:
| Subsystem | Purpose |
|---|---|
Portfolio | Manages the set of open documents, the active document, and clipboard |
Document | Owns a single document’s state: layers, history, viewport |
Tool | Active tool state, tool options, and tool interaction events |
Input | Raw keyboard and mouse input preprocessing |
Layout | UI layout definitions sent to the frontend |
Dialog | Modal dialogs and confirmation prompts |
Animation | Animation playback and timeline management |
Network | The node graph network for the current document |
Preferences | User preferences and editor settings |
Exploring the Hierarchy
The editor ships a built-in exploration tool that opens an interactive, searchable outline of the subsystem hierarchy in your browser:Adding a New Message
Define the variant
Add a new variant to the appropriate
*Message enum, with named struct-style fields for any data the handler needs.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().