Skill

Implement Makepad App State Management

A skill for Makepad app state management and persistence, based on the Robrix and Moly codebases - AppState, Scope, serde-based save/load.

Works with serdematrix sdk

91
Spark score
out of 100
Updated last month
Version 1.0.0

Add to Favorites

Why it matters

Establish robust state management and persistence patterns for Makepad applications, drawing from proven Robrix and Moly codebases. Ensure seamless state handling across UI components and sessions.

Outcomes

What it gets done

01

Design and implement application state structures.

02

Integrate state persistence mechanisms for saving and loading data.

03

Manage UI state propagation through the widget tree.

04

Handle state updates and modifications within child widgets.

Install

Add it to your toolbox

Run in your project directory:

curl -fsSL https://spark.entire.vc/get/ag-robius-state-management | bash

Overview

Robius State Management Skill

Robius State Management provides Rust patterns for Makepad app state - a serializable AppState struct, Scope-based propagation through the widget tree, and async save/load with graceful fallback on missing or corrupted state files. Use it when designing state structure or persistence for a Makepad application; not for state management in other Rust UI frameworks.

What it does

Robius State Management is a skill covering best practices for state management and persistence in Makepad applications, drawn from two real codebases: Robrix (a Matrix chat client, contributing the AppState/SelectedRoom pattern and serde-based persistence) and Moly (an AI chat application, contributing the Central Store pattern, async initialization, and Preferences). It points to a _base/ directory of production-ready patterns including global widget registry via Cx::set_global, radio-button tab navigation, enum-based state machine widgets, multi-theme switching via apply_over, and local persistence of user preferences.

It defines a core AppState struct (derived Clone, Default, Debug, Serialize, Deserialize) holding the currently selected room, saved UI layout state, per-item saved states keyed by room ID, and a #[serde(skip)]-annotated login flag that is deliberately excluded from persistence. A SelectedRoom enum models joined rooms, invited rooms, and spaces, with an upgrade_invite_to_joined method and custom PartialEq/Eq based on room ID only. A SavedLayoutState struct captures layout items, open items, item order, and the selected item at save time, using a LiveIdSerde wrapper to make Makepad's LiveId serializable.

State propagates through the widget tree via Scope::with_data(&mut self.app_state) passed into self.ui.handle_event, with child widgets reading state via scope.data.get::<AppState>() and mutating it via scope.data.get_mut::<AppState>().

The persistence layer defines platform-specific data directory resolution (a OnceLock-cached app_data_dir() plus a per-user persistent_state_dir()), synchronous save functions using serde_json::to_writer with explicit flush(), and an async load_app_state function with graceful fallback: missing file returns AppState::default(), and a deserialize failure backs up the corrupted file to a .json.bak path before falling back to defaults rather than crashing. Window geometry (size, position, fullscreen state) is saved/loaded separately and synchronously since it must be available on the UI thread before app state may finish loading asynchronously.

When to use - and when NOT to

Use this skill when designing application state structure for a Makepad app, implementing state persistence to disk, passing state through the widget tree via Scope, or managing UI state that must survive across app sessions.

Do not use it for state management in non-Makepad UI frameworks - the Scope/Cx/LiveId patterns are specific to Makepad's architecture and won't transfer directly to other Rust UI toolkits.

Inputs and outputs

Input: the application's state shape (what needs to persist vs. session-only) and where it must be accessible in the widget tree.

Output: a serializable state struct plus save/load functions, for example the async load with graceful fallback:

pub async fn load_app_state(user_id: &UserId) -> anyhow::Result<AppState> {
    let state_path = persistent_state_dir(user_id).join(LATEST_APP_STATE_FILE_NAME);

    let file_bytes = match tokio::fs::read(&state_path).await {
        Ok(fb) => fb,
        Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
            log!("No saved app state found, using default.");
            return Ok(AppState::default());
        }
        Err(e) => return Err(e.into()),
    };

    match serde_json::from_slice(&file_bytes) {
        Ok(app_state) => Ok(app_state),
        Err(e) => {
            error!("Failed to deserialize: {e}. May be incompatible format.");
            let backup_path = state_path.with_extension("json.bak");
            let _ = tokio::fs::rename(&state_path, &backup_path).await;
            Ok(AppState::default())
        }
    }
}

Along with this it produces Scope-based state propagation code and window geometry persistence.

Who it's for

Rust developers building Makepad applications who need a proven state management and persistence architecture rather than designing app-wide state and save/load logic from scratch.

FAQ

Common questions

Discussion

Questions & comments ยท 0

Sign In Sign in to leave a comment.