Token导航 LogoToken导航

Agent Skill · 前端设计

robius-state-management(罗宾斯状态管理)

用于辅助前端页面、组件、样式和交互逻辑的开发与维护。它适合让 Agent 生成或审查 React、Next.js、Vue、Tailwind、CSS 等相关代码,整理组件结构,或定位布局和性能问题。使用时需要结合项目现有设计系统、路由和构建方式,避免只生成孤立片段;涉及页面改动时,应配合本地预览和构建检查确认视觉效果。

robius-state-management 是前端设计类 Agent Skill,可用宿主为 Codex、Claude、Cursor、Gemini CLI,通过GitHub 仓库获取,权限为只读;许可证为 MIT,第三方扫描有提醒项,按安装量在前端设计类的 10,216 个 Skill 里排第 7,112。

总安装 419周安装 18GitHub Stars 737下载 147核验 2026-05-01

安装说明

本站只整理中文说明,不托管安装包,也不代用户安装。
GitHub
许可证
MIT

安装方式

通过对话安装

复制提示词发给支持本地命令或 Skills 的 AI 助手,先确认命令和权限,再让它执行。

请帮我安装这个 Agent Skill:robius-state-management(罗宾斯状态管理)
来源仓库:https://github.com/zhanghandong/makepad-skills
仓库路径:skills/robius-state-management
安装命令:
npx skills add https://github.com/zhanghandong/makepad-skills --skill robius-state-management
安装前请先检查当前环境是否支持对应 CLI,并向我确认将要执行的命令、安装目录、联网范围和文件读写权限;确认后再执行。

命令行安装

复制命令到本机终端执行。不同来源提供的安装方式可能略有差异;本站展示可直接复制的安装命令,安装前请核对来源页面。

skills.shnpx skills
npx skills add https://github.com/zhanghandong/makepad-skills --skill robius-state-management

这个 Skill 是做什么的

前端设计类 Skill 关注页面结构、视觉规范和组件写法,输出通常是可运行的界面代码。

robius-state-management 用于辅助前端页面和组件的开发与维护。适合生成或审查 React、Vue、Tailwind CSS 等相关代码。支持在 Codex、Claude、Cursor、Gemini CLI 中使用。涉及页面改动时应配合本地预览检查布局和性能问题。

适用宿主包括 Codex、Claude、Cursor、Gemini CLI,接入前应确认版本、权限和运行环境要求。

来源仓库内路径:skills/robius-state-management

上手步骤

4 步
  1. 1

    确认自己的宿主工具

    公开资料标注支持 Codex、Claude、Cursor、Gemini CLI;先确认自己用的是其中之一,再按对应工具的 Skills 目录放置。

  2. 2

    拿到安装命令

    可以直接复制的命令是 npx skills add https://github.com/zhanghandong/makepad-skills --skill robius-sta…;命令里的占位符换成自己的路径或密钥再执行。

  3. 3

    安装后重启宿主

    命令执行完要完全退出宿主工具再打开,让新装的 Skill 被重新扫描到,否则列表里可能看不到。

  4. 4

    按最小权限试用

    它标注为只读,先用只读任务试一次,确认行为符合预期再扩大使用范围。

常见问题

6 条

robius-state-management 怎么安装?

在终端执行 npx skills add https://github.com/zhanghandong/makepad-skills --skill robius-state-managem…。装完完全退出宿主工具再打开,让它重新扫描本地 Skill。

robius-state-management 支持哪些 AI 工具?

公开资料标注支持 Codex、Claude、Cursor、Gemini CLI。其他工具只要能读取本地 Skill 目录,一般也能手动放进去,但要自己确认触发方式是否一致。

robius-state-management 需要什么权限?

权限级别是只读。该 Skill 主要提供规则、说明或参考内容,本身偏只读;真正读写文件、联网或执行命令仍取决于宿主 Agent 的任务。

robius-state-management 是免费的吗?

来源标注的许可证是 MIT。本站不代收任何费用,也不托管安装包;能否商用、要不要署名,以许可证原文为准。

robius-state-management 用的人多吗?

公开数据是累计安装 419 次,近一周安装 18 次,下载 147 次,GitHub 737 星。这些数字来自第三方目录统计,只能当作热度参考,不代表质量或安全性。

robius-state-management 安全吗?

第三方扫描有提醒项,说明存在需要人工确认的行为。安装前请打开源码看清它读了什么、联网到哪里。

值得选的地方和要先想清楚的地方

值得选的地方

  • 同时适配 4 种工具,换客户端不用重新找替代品。
  • 3 个目录同时收录,说明这个 Skill 流传比较广。
  • 许可证明确为 MIT,商用和分发边界好判断。
  • 标注为只读权限,试用阶段不容易误改本地文件。

先想清楚的地方

  • 第三方扫描存在提醒项或未通过项,需要人工逐条确认。
  • 第三方目录整理,版本更新和行为细节以来源仓库为准。

适不适合你

更适合

  • 已经在用 Codex,想直接扩展它能力的人。
  • 需要处理前端设计类任务、又不想每次都手写提示词的人。
  • 只想先试试效果、不希望它改动本地文件的人。

不太适合

  • 需要厂商级技术支持和 SLA 的生产系统。

前端设计类 Skill 对比

6 个同类
Skill总安装GitHub 星权限许可证扫描
frontend-design

前端设计 · Codex、Claude

8,823,360126,072只读MIT通过
vercel-composition-patterns

Vercel 组合模式 · Codex、Claude

3,858,81625,893需要联网MIT通过
vercel-react-native-skills

Vercel React native skills 前端 · Codex、Claude

2,468,66425,878只读MIT通过
adapt

adapt 搜索 · Codex、Claude

2,027,04023,125操作浏览器未明确通过
bolder

bolder 分析 · Codex、Claude

2,004,28823,130只读未明确通过
robius-state-management

罗宾斯状态管理 · 本页

419737只读MIT有提醒

按安装量取同分类前几名;数字来自第三方目录统计,只作热度参考,不代表质量结论。

SKILL.md 原文

Robius State Management Skill

Best practices for state management and persistence in Makepad applications based on Robrix and Moly codebases.

Source codebases:

  • Robrix: Matrix chat client - AppState, SelectedRoom, persistence via serde
  • Moly: AI chat application - Central Store pattern, async initialization, Preferences

Triggers

Use this skill when:

  • Designing application state structure
  • Implementing state persistence
  • Passing state through widget tree
  • Managing UI state across sessions
  • Keywords: app state, makepad state, persistence, Scope::with_data, save state, load state

Production Patterns

For production-ready state management patterns, see the _base/ directory:

PatternDescription
06-global-registryGlobal widget registry with Cx::set_global
07-radio-navigationTab-style navigation with radio buttons
10-state-machineEnum-based state machine widgets
11-theme-switchingMulti-theme support with apply_over
12-local-persistenceSave/load user preferences

AppState Structure

Core State Definition

use serde::{Serialize, Deserialize};
use std::collections::HashMap;
use matrix_sdk::ruma::OwnedRoomId;

/// App-wide state that is stored persistently across multiple app runs
/// and shared/updated across various parts of the app.
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct AppState {
    /// The currently-selected room
    pub selected_room: Option<SelectedRoom>,

    /// Saved UI layout state for main view
    pub saved_layout_state: SavedLayoutState,

    /// Per-item saved states (e.g., per-space dock layouts)
    pub saved_state_per_item: HashMap<OwnedRoomId, SavedLayoutState>,

    /// Whether a user is currently logged in
    #[serde(skip)]  // Don't persist login state
    pub logged_in: bool,
}

/// Represents a currently selected item
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum SelectedRoom {
    JoinedRoom { room_name_id: RoomNameId },
    InvitedRoom { room_name_id: RoomNameId },
    Space { space_name_id: RoomNameId },
}

impl SelectedRoom {
    pub fn room_id(&self) -> &OwnedRoomId {
        match self {
            Self::JoinedRoom { room_name_id } => room_name_id.room_id(),
            Self::InvitedRoom { room_name_id } => room_name_id.room_id(),
            Self::Space { space_name_id } => space_name_id.room_id(),
        }
    }

    /// Upgrade from invited to joined state
    pub fn upgrade_invite_to_joined(&mut self, room_id: &RoomId) -> bool {
        match self {
            Self::InvitedRoom { room_name_id } if room_name_id.room_id() == room_id => {
                let name = room_name_id.clone();
                *self = Self::JoinedRoom { room_name_id: name };
                true
            }
            _ => false,
        }
    }
}

// Equality based on room_id only
impl PartialEq for SelectedRoom {
    fn eq(&self, other: &Self) -> bool {
        self.room_id() == other.room_id()
    }
}
impl Eq for SelectedRoom {}

Layout/Dock State Persistence

/// A snapshot of UI layout state for restoration
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct SavedLayoutState {
    /// All items contained in the layout, keyed by ID
    pub layout_items: HashMap<LiveIdSerde, LayoutItemSerde>,

    /// Items currently open, keyed by ID
    pub open_items: HashMap<LiveIdSerde, SelectedRoom>,

    /// Order items were opened (chronological)
    pub item_order: Vec<SelectedRoom>,

    /// Currently selected item when state was saved
    pub selected_item: Option<SelectedRoom>,
}

/// Serializable wrapper for LiveId
#[derive(Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)]
pub struct LiveIdSerde(pub u64);

impl From<LiveId> for LiveIdSerde {
    fn from(id: LiveId) -> Self {
        Self(id.0)
    }
}

impl From<LiveIdSerde> for LiveId {
    fn from(s: LiveIdSerde) -> Self {
        LiveId(s.0)
    }
}

State Propagation via Scope

Passing State Through Widget Tree

impl AppMain for App {
    fn handle_event(&mut self, cx: &mut Cx, event: &Event) {
        // Forward to MatchEvent
        self.match_event(cx, event);

        // Create Scope with AppState data
        let scope = &mut Scope::with_data(&mut self.app_state);

        // Pass to widget tree - all children can access AppState
        self.ui.handle_event(cx, event, scope);
    }
}

Accessing State in Child Widgets

impl Widget for RoomScreen {
    fn handle_event(&mut self, cx: &mut Cx, event: &Event, scope: &mut Scope) {
        // Access AppState from scope
        if let Some(app_state) = scope.data.get::<AppState>() {
            if let Some(selected) = &app_state.selected_room {
                self.update_for_room(cx, selected);
            }
        }

        self.view.handle_event(cx, event, scope);
    }
}

Modifying State

impl Widget for RoomsList {
    fn handle_event(&mut self, cx: &mut Cx, event: &Event, scope: &mut Scope) {
        // Mutable access to AppState
        if let Some(app_state) = scope.data.get_mut::<AppState>() {
            if self.selection_changed {
                app_state.selected_room = self.get_selected();
            }
        }
    }
}

Persistence Layer

File Paths

use std::path::{Path, PathBuf};

const LATEST_APP_STATE_FILE_NAME: &str = "latest_app_state.json";
const WINDOW_GEOM_STATE_FILE_NAME: &str = "window_geom_state.json";

/// Get user-specific persistent state directory
fn persistent_state_dir(user_id: &UserId) -> PathBuf {
    app_data_dir()
        .join("users")
        .join(user_id.to_string().replace(':', "_"))
}

/// Get app-wide data directory
fn app_data_dir() -> &'static Path {
    // Platform-specific app data location
    static APP_DATA_DIR: OnceLock<PathBuf> = OnceLock::new();
    APP_DATA_DIR.get_or_init(|| {
        dirs::data_dir()
            .unwrap_or_else(|| PathBuf::from("."))
            .join("myapp")
    })
}

Saving State

use std::io::Write;

pub fn save_app_state(
    app_state: AppState,
    user_id: OwnedUserId,
) -> anyhow::Result<()> {
    let file = std::fs::File::create(
        persistent_state_dir(&user_id).join(LATEST_APP_STATE_FILE_NAME)
    )?;
    let mut writer = std::io::BufWriter::new(file);
    serde_json::to_writer(&mut writer, &app_state)?;
    writer.flush()?;
    log!("Successfully saved app state to persistent storage.");
    Ok(())
}

/// Save window geometry state (user-agnostic)
pub fn save_window_state(window_ref: WindowRef, cx: &Cx) -> anyhow::Result<()> {
    let inner_size = window_ref.get_inner_size(cx);
    let position = window_ref.get_position(cx);
    let window_geom = WindowGeomState {
        inner_size: (inner_size.x, inner_size.y),
        position: (position.x, position.y),
        is_fullscreen: window_ref.is_fullscreen(cx),
    };
    std::fs::write(
        app_data_dir().join(WINDOW_GEOM_STATE_FILE_NAME),
        serde_json::to_string(&window_geom)?,
    )?;
    Ok(())
}

Loading State

/// Load app state 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);

    // Read file
    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()),
    };

    // Deserialize with fallback
    match serde_json::from_slice(&file_bytes) {
        Ok(app_state) => {
            log!("Successfully loaded app state.");
            Ok(app_state)
        }
        Err(e) => {
            error!("Failed to deserialize: {e}. May be incompatible format.");

            // Backup old file
            let backup_path = state_path.with_extension("json.bak");
            if let Err(backup_err) = tokio::fs::rename(&state_path, &backup_path).await {
                error!("Failed to backup old state: {}", backup_err);
            } else {
                log!("Old state backed up to: {:?}", backup_path);
            }

            log!("Using default app state.");
            Ok(AppState::default())
        }
    }
}

/// Load window geometry (synchronous, on UI thread)
pub fn load_window_state(window_ref: WindowRef, cx: &mut Cx) -> anyhow::Result<()> {
    let file = match std::fs::File::open(app_data_dir().join(WINDOW_GEOM_STATE_FILE_NAME)) {
        Ok(file) => file,
        Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Ok(()),
        Err(e) => return Err(e.into()),
    };

    let window_geom: WindowGeomState = serde_json::from_reader(file)?;
    log!("Restoring window geometry: {window_geom:?}");

    window_ref.configure_window(
        cx,
        dvec2(window_geom.inner_size.0, window_geom.inner_size.1),
        dvec2(window_geom.position.0, window_geom.position.1),
        window_geom.is_fullscreen,
        "MyApp".to_string(),
    );
    Ok(())
}

Startup/Shutdown Integration

impl MatchEvent for App {
    fn handle_startup(&mut self, cx: &mut Cx) {
        // Load window geometry (sync, on UI thread)
        if let Err(e) = persistence::load_window_state(
            self.ui.window(ids!(main_window)), cx
        ) {
            error!("Failed to load window state: {}", e);
        }

        // Trigger async app state load
        let user_id = get_current_user_id();
        tokio::spawn(async move {
            match persistence::load_app_state(&user_id).await {
                Ok(app_state) => {
                    Cx::post_action(AppStateAction::RestoreFromPersistence(app_state));
                    SignalToUI::set_ui_signal();
                }
                Err(e) => error!("Failed to load app state: {}", e),
            }
        });
    }
}

impl AppMain for App {
    fn handle_event(&mut self, cx: &mut Cx, event: &Event) {
        if let Event::Shutdown = event {
            // Save window state (sync)
            if let Err(e) = persistence::save_window_state(
                self.ui.window(ids!(main_window)), cx
            ) {
                error!("Failed to save window state: {e}");
            }

            // Save app state (sync)
            if let Some(user_id) = current_user_id() {
                if let Err(e) = persistence::save_app_state(
                    self.app_state.clone(), user_id
                ) {
                    error!("Failed to save app state: {e}");
                }
            }
        }
        // ...
    }
}

Thread-Local State (UI-Only)

use std::{cell::RefCell, rc::Rc, collections::HashMap};

thread_local! {
    /// UI-thread-only cache
    static UI_CACHE: Rc<RefCell<HashMap<OwnedRoomId, CachedData>>> =
        Rc::new(RefCell::new(HashMap::new()));
}

/// Get cache reference (requires Cx to ensure UI thread)
pub fn get_ui_cache(_cx: &mut Cx) -> Rc<RefCell<HashMap<OwnedRoomId, CachedData>>> {
    UI_CACHE.with(Rc::clone)
}

/// Clear cache (requires Cx)
pub fn clear_ui_cache(_cx: &mut Cx) {
    UI_CACHE.with(|cache| cache.borrow_mut().clear());
}

Best Practices

  1. Separate persistent vs runtime state: Use #[serde(skip)] for non-persistent fields
  2. Use Scope::with_data() for tree propagation: Don't pass state through widget refs
  3. Graceful deserialization fallback: Handle format changes between versions
  4. Backup old state files: Preserve user data when format changes
  5. User-specific persistent paths: Separate state per user account
  6. Sync window state, async app state: Window geometry loads sync on UI thread
  7. Thread-local for UI-only caches: Use thread_local! with Cx parameter guard

Reference Files

  • references/persistence-patterns.md - Additional persistence patterns (Robrix)
  • references/state-structures.md - State structure examples (Robrix)
  • references/moly-state-patterns.md - Moly-specific patterns

- Central Store struct containing all state - Async Store initialization with load_into_app() - App state check pattern (early return if not loaded) - Submodule state managers (Search, Downloads, Chats) - Provider syncing status tracking - Store action forwarding to submodules

平台分布

OpenCode

28.75%
按下载量换算42

Codex

21.62%
按下载量换算32

Claude Code

19.55%
按下载量换算29

Antigravity

14.45%
按下载量换算21

Gemini CLI

8.94%
按下载量换算13

windsurf

3.79%
按下载量换算6

安全审计

Gen Agent Trust Hub

通过

Socket

通过

Snyk

可疑

权限和风险

只读

该 Skill 主要提供规则、说明或参考内容,本身偏只读;真正读写文件、联网或执行命令仍取决于宿主 Agent 的任务。

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。来源安全扫描存在提醒或未通过项,安装前请自行复核。

继续浏览同类 Skills