[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill-f93920e0-3595-401a-ae57-38760b9a68f0":3,"$fpZGe-fb9GsWAqth0SZxky743nDhDdETp_qBH5Ics0Tc":43},{"id":4,"title":5,"description":6,"categoryId":7,"moduleId":8,"tags":9,"prompt":10,"icon":11,"source":12,"sourceUrl":13,"authorId":14,"authorName":15,"isPublic":16,"stars":17,"runs":18,"createdAt":19,"updatedAt":19,"module":20,"category":27,"packages":34},"f93920e0-3595-401a-ae57-38760b9a68f0","robius-matrix-integration","|","cat_life_career","mod_other","sickn33,other","---\nname: robius-matrix-integration\ndescription: |\n  CRITICAL: Use for Matrix SDK integration with Makepad. Triggers on:\n  Matrix SDK, sliding sync, MatrixRequest, timeline,\n  matrix-sdk, matrix client, robrix, matrix room,\n  Matrix 集成, 聊天客户端\nrisk: unknown\nsource: community\n---\n\n# Robius Matrix SDK Integration Skill\n\nBest practices for integrating external APIs with Makepad applications based on Robrix and Moly codebases.\n\n**Source codebases:**\n- **Robrix**: Matrix SDK integration - sliding sync, timeline subscriptions, real-time updates\n- **Moly**: OpenAI\u002FLLM API integration - SSE streaming, MCP protocol, multi-provider support\n\n## When to Use\nUse this skill when:\n- Integrating Matrix SDK with Makepad\n- Building a Matrix client with Makepad\n- Implementing Matrix features (rooms, timelines, messages)\n- Handling Matrix SDK async operations in UI\n- Keywords: matrix-sdk, matrix client, robrix, matrix timeline, matrix room, sliding sync\n\n## Overview\n\nRobrix uses the `matrix-sdk` and `matrix-sdk-ui` crates to connect to Matrix homeservers. The key architectural decisions:\n\n1. **Sliding Sync**: Uses native sliding sync for efficient room list updates\n2. **Separate Runtime**: Tokio runtime runs Matrix operations, Makepad handles UI\n3. **Request\u002FResponse Pattern**: UI sends requests, receives actions\u002Fupdates back\n4. **Per-Room Background Tasks**: Each room has dedicated timeline subscriber task\n\n## MatrixRequest Pattern\n\n### Request Enum Definition\n\n```rust\n\u002F\u002F\u002F All async requests that can be made to the Matrix worker task\npub enum MatrixRequest {\n    \u002F\u002F\u002F Login requests\n    Login(LoginRequest),\n    Logout { is_desktop: bool },\n\n    \u002F\u002F\u002F Timeline operations\n    PaginateRoomTimeline {\n        room_id: OwnedRoomId,\n        num_events: u16,\n        direction: PaginationDirection,\n    },\n    SendMessage {\n        room_id: OwnedRoomId,\n        message: RoomMessageEventContent,\n        replied_to: Option\u003CReply>,\n    },\n    EditMessage {\n        room_id: OwnedRoomId,\n        timeline_event_item_id: TimelineEventItemId,\n        edited_content: EditedContent,\n    },\n    RedactMessage {\n        room_id: OwnedRoomId,\n        timeline_event_id: TimelineEventItemId,\n        reason: Option\u003CString>,\n    },\n\n    \u002F\u002F\u002F Room operations\n    JoinRoom { room_id: OwnedRoomId },\n    LeaveRoom { room_id: OwnedRoomId },\n    GetRoomMembers {\n        room_id: OwnedRoomId,\n        memberships: RoomMemberships,\n        local_only: bool,\n    },\n\n    \u002F\u002F\u002F User operations\n    GetUserProfile {\n        user_id: OwnedUserId,\n        room_id: Option\u003COwnedRoomId>,\n        local_only: bool,\n    },\n    IgnoreUser {\n        ignore: bool,\n        room_member: RoomMember,\n        room_id: OwnedRoomId,\n    },\n\n    \u002F\u002F\u002F Media operations\n    FetchAvatar {\n        mxc_uri: OwnedMxcUri,\n        on_fetched: fn(AvatarUpdate),\n    },\n    FetchMedia {\n        media_request: MediaRequestParameters,\n        on_fetched: OnMediaFetchedFn,\n        destination: MediaCacheEntryRef,\n        update_sender: Option\u003Ccrossbeam_channel::Sender\u003CTimelineUpdate>>,\n    },\n\n    \u002F\u002F\u002F Typing\u002Fread indicators\n    SendTypingNotice { room_id: OwnedRoomId, typing: bool },\n    ReadReceipt { room_id: OwnedRoomId, event_id: OwnedEventId },\n    FullyReadReceipt { room_id: OwnedRoomId, event_id: OwnedEventId },\n\n    \u002F\u002F\u002F Reactions\n    ToggleReaction {\n        room_id: OwnedRoomId,\n        timeline_event_id: TimelineEventItemId,\n        reaction: String,\n    },\n\n    \u002F\u002F\u002F Subscriptions\n    SubscribeToTypingNotices { room_id: OwnedRoomId, subscribe: bool },\n    SubscribeToPinnedEvents { room_id: OwnedRoomId, subscribe: bool },\n}\n```\n\n### Submit Pattern\n\n```rust\nstatic REQUEST_SENDER: Mutex\u003COption\u003CUnboundedSender\u003CMatrixRequest>>> = Mutex::new(None);\n\n\u002F\u002F\u002F Submit request from UI thread to async runtime\npub fn submit_async_request(req: MatrixRequest) {\n    if let Some(sender) = REQUEST_SENDER.lock().unwrap().as_ref() {\n        sender.send(req).expect(\"BUG: matrix worker task receiver died!\");\n    }\n}\n\n\u002F\u002F Usage in UI\nsubmit_async_request(MatrixRequest::SendMessage {\n    room_id: room_id.clone(),\n    message: RoomMessageEventContent::text_plain(&text),\n    replied_to: self.reply_to.take(),\n});\n```\n\n## Worker Task Handler\n\n```rust\nasync fn matrix_worker_task(\n    mut request_receiver: UnboundedReceiver\u003CMatrixRequest>,\n    login_sender: Sender\u003CLoginRequest>,\n) -> Result\u003C()> {\n    while let Some(request) = request_receiver.recv().await {\n        match request {\n            MatrixRequest::PaginateRoomTimeline { room_id, num_events, direction } => {\n                let (timeline, sender) = {\n                    let rooms = ALL_JOINED_ROOMS.lock().unwrap();\n                    let Some(room_info) = rooms.get(&room_id) else {\n                        continue;  \u002F\u002F Room not ready yet\n                    };\n                    (room_info.timeline.clone(), room_info.update_sender.clone())\n                };\n\n                \u002F\u002F Spawn dedicated task for this operation\n                Handle::current().spawn(async move {\n                    \u002F\u002F Notify UI pagination is starting\n                    sender.send(TimelineUpdate::PaginationRunning(direction)).unwrap();\n                    SignalToUI::set_ui_signal();\n\n                    \u002F\u002F Perform pagination\n                    let res = if direction == PaginationDirection::Forwards {\n                        timeline.paginate_forwards(num_events).await\n                    } else {\n                        timeline.paginate_backwards(num_events).await\n                    };\n\n                    \u002F\u002F Send result to UI\n                    match res {\n                        Ok(fully_paginated) => {\n                            sender.send(TimelineUpdate::PaginationIdle {\n                                fully_paginated,\n                                direction,\n                            }).unwrap();\n                        }\n                        Err(error) => {\n                            sender.send(TimelineUpdate::PaginationError {\n                                error,\n                                direction,\n                            }).unwrap();\n                        }\n                    }\n                    SignalToUI::set_ui_signal();\n                });\n            }\n\n            MatrixRequest::JoinRoom { room_id } => {\n                let Some(client) = get_client() else { continue };\n\n                Handle::current().spawn(async move {\n                    let result_action = if let Some(room) = client.get_room(&room_id) {\n                        match room.join().await {\n                            Ok(()) => JoinRoomResultAction::Joined { room_id },\n                            Err(e) => JoinRoomResultAction::Failed { room_id, error: e },\n                        }\n                    } else {\n                        match client.join_room_by_id(&room_id).await {\n                            Ok(_) => JoinRoomResultAction::Joined { room_id },\n                            Err(e) => JoinRoomResultAction::Failed { room_id, error: e },\n                        }\n                    };\n                    Cx::post_action(result_action);\n                });\n            }\n            \u002F\u002F ... handle other requests\n        }\n    }\n    Ok(())\n}\n```\n\n## Timeline Updates\n\n### TimelineUpdate Enum\n\n```rust\npub enum TimelineUpdate {\n    \u002F\u002F\u002F New items added to timeline\n    NewItems {\n        new_items: Vector\u003CArc\u003CTimelineItem>>,\n        changed_indices: BTreeSet\u003Cusize>,\n        is_append: bool,\n    },\n    \u002F\u002F\u002F Pagination state changes\n    PaginationRunning(PaginationDirection),\n    PaginationIdle {\n        fully_paginated: bool,\n        direction: PaginationDirection,\n    },\n    PaginationError {\n        error: Error,\n        direction: PaginationDirection,\n    },\n    \u002F\u002F\u002F Message edit result\n    MessageEdited {\n        timeline_event_id: TimelineEventItemId,\n        result: Result\u003C(), Error>,\n    },\n    \u002F\u002F\u002F Room members fetched\n    RoomMembersListFetched {\n        members: Vec\u003CRoomMember>,\n        sort: PrecomputedMemberSort,\n        is_local_fetch: bool,\n    },\n    \u002F\u002F\u002F Unread count updated\n    NewUnreadMessagesCount(UnreadMessageCount),\n    \u002F\u002F\u002F User power levels fetched\n    UserPowerLevels(UserPowerLevels),\n}\n```\n\n### Per-Room Update Flow\n\n```rust\nstruct JoinedRoomDetails {\n    room_id: OwnedRoomId,\n    timeline: Arc\u003CTimeline>,\n    timeline_update_sender: crossbeam_channel::Sender\u003CTimelineUpdate>,\n    timeline_subscriber_handler_task: JoinHandle\u003C()>,\n    typing_notice_subscriber: Option\u003CEventHandlerDropGuard>,\n}\n\nimpl Drop for JoinedRoomDetails {\n    fn drop(&mut self) {\n        \u002F\u002F Cleanup background tasks when room closes\n        self.timeline_subscriber_handler_task.abort();\n        drop(self.typing_notice_subscriber.take());\n    }\n}\n\n\u002F\u002F Spawn subscriber for a room\nasync fn spawn_timeline_subscriber(\n    room_id: OwnedRoomId,\n    timeline: Arc\u003CTimeline>,\n    sender: crossbeam_channel::Sender\u003CTimelineUpdate>,\n) -> JoinHandle\u003C()> {\n    tokio::spawn(async move {\n        let (items, mut stream) = timeline.subscribe().await;\n\n        \u002F\u002F Send initial items\n        sender.send(TimelineUpdate::NewItems {\n            new_items: items,\n            changed_indices: BTreeSet::new(),\n            is_append: false,\n        }).unwrap();\n        SignalToUI::set_ui_signal();\n\n        \u002F\u002F Listen for updates\n        while let Some(diff) = stream.next().await {\n            let update = process_timeline_diff(diff);\n            sender.send(update).unwrap();\n            SignalToUI::set_ui_signal();\n        }\n    })\n}\n```\n\n### Handling Updates in UI\n\n```rust\nimpl Widget for RoomScreen {\n    fn handle_event(&mut self, cx: &mut Cx, event: &Event, scope: &mut Scope) {\n        \u002F\u002F Poll timeline updates on Signal events\n        if let Event::Signal = event {\n            while let Ok(update) = self.timeline_state.update_receiver.try_recv() {\n                match update {\n                    TimelineUpdate::NewItems { new_items, changed_indices, is_append } => {\n                        self.apply_new_items(cx, new_items, changed_indices, is_append);\n                    }\n                    TimelineUpdate::PaginationIdle { fully_paginated, direction } => {\n                        self.set_pagination_idle(cx, direction, fully_paginated);\n                    }\n                    TimelineUpdate::PaginationError { error, direction } => {\n                        self.show_pagination_error(cx, direction, &error);\n                    }\n                    \u002F\u002F ... handle other updates\n                }\n            }\n        }\n\n        self.view.handle_event(cx, event, scope);\n    }\n}\n```\n\n## Room List Updates\n\n### RoomsListUpdate Enum\n\n```rust\npub enum RoomsListUpdate {\n    NotLoaded,\n    LoadedRooms { max_rooms: Option\u003Cu32> },\n    AddInvitedRoom(InvitedRoomInfo),\n    AddJoinedRoom(JoinedRoomInfo),\n    ClearRooms,\n    UpdateLatestEvent {\n        room_id: OwnedRoomId,\n        timestamp: MilliSecondsSinceUnixEpoch,\n        latest_message_text: String,\n    },\n    UpdateNumUnreadMessages {\n        room_id: OwnedRoomId,\n        unread_messages: UnreadMessageCount,\n        unread_mentions: u64,\n    },\n    UpdateRoomName { new_room_name: RoomNameId },\n    UpdateRoomAvatar { room_id: OwnedRoomId, avatar: FetchedRoomAvatar },\n    RemoveRoom { room_id: OwnedRoomId, new_state: RoomState },\n    Status { status: String },\n    ScrollToRoom(OwnedRoomId),\n}\n\nstatic PENDING_ROOM_UPDATES: SegQueue\u003CRoomsListUpdate> = SegQueue::new();\n\npub fn enqueue_rooms_list_update(update: RoomsListUpdate) {\n    PENDING_ROOM_UPDATES.push(update);\n    SignalToUI::set_ui_signal();\n}\n```\n\n## Client Build Pattern\n\n```rust\nasync fn build_client(\n    homeserver_url: &str,\n    data_dir: &Path,\n) -> Result\u003C(Client, ClientSessionPersisted)> {\n    \u002F\u002F Generate unique subfolder for this session\n    let db_subfolder = format!(\"db_{}\", chrono::Local::now().format(\"%F_%H_%M_%S_%f\"));\n    let db_path = data_dir.join(db_subfolder);\n\n    \u002F\u002F Generate random passphrase for encryption\n    let passphrase: String = {\n        use rand::{Rng, thread_rng};\n        thread_rng()\n            .sample_iter(rand::distributions::Alphanumeric)\n            .take(32)\n            .map(char::from)\n            .collect()\n    };\n\n    let client = Client::builder()\n        .server_name_or_homeserver_url(homeserver_url)\n        .sqlite_store(&db_path, Some(&passphrase))\n        .sliding_sync_version_builder(VersionBuilder::DiscoverNative)\n        .with_decryption_settings(DecryptionSettings {\n            sender_device_trust_requirement: TrustRequirement::Untrusted,\n        })\n        .with_encryption_settings(EncryptionSettings {\n            auto_enable_cross_signing: true,\n            backup_download_strategy: BackupDownloadStrategy::OneShot,\n            auto_enable_backups: true,\n        })\n        .request_config(\n            RequestConfig::new().timeout(Duration::from_secs(60))\n        )\n        .build()\n        .await?;\n\n    Ok((client, ClientSessionPersisted { homeserver: homeserver_url.to_string(), db_path, passphrase }))\n}\n```\n\n## Best Practices\n\n1. **Always spawn tasks**: Don't block the worker task receiver loop\n2. **Use crossbeam channels for per-room updates**: More efficient than global queue\n3. **Always call SignalToUI::set_ui_signal()**: After enqueueing any update\n4. **Handle room not ready**: Skip requests for rooms not yet in `ALL_JOINED_ROOMS`\n5. **Cleanup on drop**: Abort background tasks when rooms are closed\n6. **Use Cx::post_action for results**: Posted actions are handled in App::handle_actions\n7. **Use SegQueue for high-frequency updates**: Lock-free for room list updates\n\n## Reference Files\n\n- `references\u002Fmatrix-client.md` - Matrix client setup and login patterns (Robrix)\n- `references\u002Ftimeline-handling.md` - Matrix timeline subscription patterns (Robrix)\n- `references\u002Fmoly-api-integration.md` - Moly API integration patterns\n  - OpenAI client with SSE streaming\n  - Platform-agnostic async streams\n  - MCP (Model Context Protocol) integration\n  - Tool approval flow\n  - MolyClient for local server\n  - BotContext for multi-provider support\n\n## Limitations\n- Use this skill only when the task clearly matches the scope described above.\n- Do not treat the output as a substitute for environment-specific validation, testing, or expert review.\n- Stop and ask for clarification if required inputs, permissions, safety boundaries, or success criteria are missing.\n","","imported","https:\u002F\u002Fgithub.com\u002Fsickn33\u002Fantigravity-awesome-skills","user_system_seed","SkillOPIC",true,165,328,"2026-05-16 13:37:20",{"id":8,"name":21,"slug":22,"icon":23,"description":24,"sort":25,"createdAt":26},"其他","other","mdi-page-next-outline","其他类型Skill",5,"2026-05-16 12:53:40",{"id":7,"name":28,"slug":29,"icon":30,"description":31,"moduleId":8,"sort":32,"skillCount":33,"createdAt":26},"职场发展","career","mdi-briefcase-outline","面试准备、简历优化、职业规划",4,575,[35],{"id":36,"skillId":4,"version":37,"fileName":38,"fileSize":39,"filePath":40,"fileHash":41,"manifest":42,"createdAt":19},"53cd9042-2b51-434c-b9b7-da4cfdb1abfb","1.0.0","robius-matrix-integration.zip",4729,"uploads\u002Fskills\u002Ff93920e0-3595-401a-ae57-38760b9a68f0\u002Frobius-matrix-integration.zip","d18b03288e0f9f55281ad8a5ae35046cf543d555dc0586f2511c70de2fe8ade8","[{\"path\":\"SKILL.md\",\"isDirectory\":false,\"size\":14370}]",{"code":44,"message":45,"data":46},200,"success",{"items":47,"stats":48,"page":51},[],{"averageRating":49,"totalRatings":49,"ratingCounts":50},0,[49,49,49,49,49],{"limit":52,"offset":49,"hasMore":53,"nextOffset":52,"ratedOnly":16},15,false]