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.

Graphite has a clear architectural split between its Svelte/TypeScript frontend and its Rust backend. The frontend is responsible for rendering the user interface: panels, toolbars, the canvas overlay, and all interactive widgets. The Rust backend, compiled to WebAssembly, handles all editing logic — document state, tool behavior, history, and node graph execution. Neither side directly inspects the other’s internal state; all communication happens through a message-passing protocol across the Wasm boundary.

Architecture Overview

The data flow between frontend and backend follows this path:
User Action (click, key press, menu)


  Svelte Component
        │  dispatches message

  EditorWrapper (wasm-bindgen)
        │  serializes + calls into Wasm

  Rust Editor Backend (Wasm)
        │  processes message, queues responses

  Response Callback
        │  deserializes response messages

  SubscriptionsRouter
        │  routes to registered handlers

  Svelte Stores / Component Updates
The frontend sends input events and user actions to the Wasm backend as typed messages. The backend processes them and fires back response messages that instruct the frontend to update specific parts of the UI — the layers panel, properties panel, canvas, or dialogs.

wasm-bindgen

The frontend/wrapper crate is the glue layer between JavaScript and Rust. It uses wasm-bindgen to expose Rust types and functions to TypeScript. The EditorWrapper struct is the primary entry point: it is instantiated once on startup and provides methods that the frontend calls to dispatch messages into the editor. The wasm-bindgen macro handles serialization automatically for primitive types. Richer message payloads are serialized as JSON using serde-wasm-bindgen, letting Rust structs and TypeScript interfaces stay in sync via generated type definitions.
import { EditorWrapper, receiveNativeMessage } from "/wrapper/pkg/graphite_wasm_wrapper";

// Create the editor, passing a callback for response messages
editor = await EditorWrapper.create(
    operatingSystem(),
    randomSeed,
    (messageType: MessageName, messageData: FrontendMessage) => {
        subscriptions?.handleFrontendMessage(messageType, messageData);
    }
);

Message Protocol

The frontend dispatches events by calling methods on EditorWrapper — for example, pointer move events, key presses, and menu actions. The Rust backend returns messages to the frontend by invoking the callback passed at construction time. Backend response messages follow a tagged-union format. Messages with data are serialized as { "MessageName": { ...payload } } and messages with no data are serialized as the bare string "MessageName". The SubscriptionsRouter normalizes these two forms before routing. Common response message categories include:
  • Layout updatesUpdateLayout carries widget diffs for the properties panel, layers panel, and toolbar
  • Document state — layer tree changes, node graph changes, selection changes
  • Canvas — render requests, viewport updates, overlay drawing
  • Dialogs — show/hide modal dialogs

Svelte Frontend Structure

The frontend source lives in frontend/src/:
PathPurpose
App.svelteRoot component — initializes Wasm, creates the EditorWrapper and SubscriptionsRouter, then mounts the editor
components/All Svelte UI components: panels, toolbars, dialogs, canvas
managers/Non-component modules managing cross-cutting concerns: input.ts (keyboard/mouse), clipboard.ts, persistence.ts, panic.ts
stores/Svelte reactive stores holding UI state derived from backend responses
utility-functions/Helpers for platform detection, Wasm loading, and network requests
subscriptions-router.tsThe message routing layer between Wasm callbacks and Svelte handlers
App.svelte is intentionally minimal. It initializes the Wasm module, registers node implementations, creates the EditorWrapper, and passes the SubscriptionsRouter and editor handles down to the <Editor> component:
<script lang="ts">
    import { onMount, onDestroy } from "svelte";
    import Editor from "/src/components/Editor.svelte";
    import { createSubscriptionsRouter } from "/src/subscriptions-router";
    import { initWasm } from "/src/utility-functions/wasm-loader";
    import { EditorWrapper } from "/wrapper/pkg/graphite_wasm_wrapper";

    let subscriptions = undefined;
    let editor = undefined;

    onMount(async () => {
        const wrapper = await initWasm();
        // Register all node implementations into the Wasm module
        for (const [name, f] of Object.entries(wrapper)) {
            if (name.startsWith("__node_registry")) f();
        }

        subscriptions = createSubscriptionsRouter();
        editor = await EditorWrapper.create(
            operatingSystem(),
            randomSeed,
            (messageType, messageData) => {
                subscriptions?.handleFrontendMessage(messageType, messageData);
            }
        );
    });

    onDestroy(() => { editor?.free(); });
</script>

Subscriptions Router

frontend/src/subscriptions-router.ts is the central dispatch table that routes backend response messages to the correct Svelte handler. Components and stores register callbacks for specific message types using subscribeFrontendMessage. Layout update messages have a special routing path: they are dispatched to layout-specific callbacks keyed by LayoutTarget rather than the generic subscription map.
const subscriptions = createSubscriptionsRouter();

// Register a handler for a specific backend message type
subscriptions.subscribeFrontendMessage("UpdateDocumentLayerTree", (data) => {
    // Update Svelte store with new layer tree data
    layerTreeStore.set(data.layerWidgets);
});
The router includes a retry mechanism: if a message arrives before a component has mounted and registered its handler (a race condition possible during Wasm initialization), the router retries delivery up to three times on subsequent stack frames.

Hot Reload Dev Server

During development, the cargo run tool starts a watcher that monitors both Rust and Svelte/TypeScript source files:
  • Svelte/TypeScript changes trigger an instant Vite hot-module reload with no Wasm recompile needed
  • Rust changes trigger a wasm-pack recompile and a full browser reload once the new Wasm module is ready
This split means that frontend layout and styling iteration is fast (subsecond reloads), while backend logic changes take slightly longer due to the Rust compile and Wasm pack steps.
The frontend/wrapper crate is the only Rust crate that directly imports wasm-bindgen. All other crates in the workspace are platform-agnostic and can be compiled for both Wasm and native targets.
For more about the editor’s internal message system, see Editor Architecture. For running the engine from the command line without the frontend, see graphene-cli.

Build docs developers (and LLMs) love