{"id":"412f800e-d519-4fbb-b804-5867b096f096","shortId":"Y88zwH","kind":"skill","title":"robius-app-architecture","tagline":"CRITICAL: Use for Robius app architecture patterns. Triggers on:\nTokio, async, submit_async_request, 异步, 架构,\nSignalToUI, Cx::post_action, worker task,\napp structure, MatchEvent, handle_startup","description":"# Robius App Architecture Skill\n\nBest practices for structuring Makepad applications based on the Robrix and Moly codebases - production applications built with Makepad and Robius framework.\n\n**Source codebases:**\n- **Robrix**: Matrix chat client - complex sync/async with background subscriptions\n- **Moly**: AI chat application - cross-platform (native + WASM) with streaming APIs\n\n## When to Use\nUse this skill when:\n- Building a Makepad application with async backend integration\n- Designing sync/async communication patterns in Makepad\n- Structuring a Robius-style application\n- Keywords: robrix, robius, makepad app structure, async makepad, tokio makepad\n\n## Production Patterns\n\nFor production-ready async patterns, see the `_base/` directory:\n\n| Pattern | Description |\n|---------|-------------|\n| 08-async-loading | Async data loading with loading states |\n| 09-streaming-results | Incremental results with SignalToUI |\n| 13-tokio-integration | Full tokio runtime integration |\n\n## Core Architecture Pattern\n\n```\n┌─────────────────────────────────────────────────────────────┐\n│                     UI Thread (Makepad)                     │\n│  ┌─────────┐     ┌──────────┐     ┌──────────────────────┐ │\n│  │   App   │────▶│ WidgetRef │────▶│ Widget Tree (View)  │ │\n│  │ State   │     │    ui     │     │ Scope::with_data()  │ │\n│  └────┬────┘     └──────────┘     └──────────────────────┘ │\n│       │                                                     │\n│       │ submit_async_request()                              │\n│       ▼                                                     │\n│  ┌─────────────────┐          ┌─────────────────────────┐  │\n│  │ REQUEST_SENDER  │─────────▶│  Crossbeam SegQueue     │  │\n│  │ (MPSC Channel)  │          │  (Lock-free Updates)    │  │\n│  └─────────────────┘          └─────────────────────────┘  │\n└───────────────────────────────────┬─────────────────────────┘\n                                    │\n                    SignalToUI::set_ui_signal()\n                                    │\n┌───────────────────────────────────┴─────────────────────────┐\n│                   Tokio Runtime (Async)                      │\n│  ┌──────────────────────────────────────────────────────┐   │\n│  │           worker_task (Request Handler)               │   │\n│  │  - Receives Request from UI                           │   │\n│  │  - Spawns async tasks per request                     │   │\n│  │  - Posts actions back via Cx::post_action()           │   │\n│  └──────────────────────────────────────────────────────┘   │\n│  ┌──────────────────────────────────────────────────────┐   │\n│  │         Per-Item Subscriber Tasks                     │   │\n│  │  - Listens to external data stream                    │   │\n│  │  - Sends Update via crossbeam channel                 │   │\n│  │  - Calls SignalToUI::set_ui_signal() to wake UI       │   │\n│  └──────────────────────────────────────────────────────┘   │\n└─────────────────────────────────────────────────────────────┘\n```\n\n## App Structure\n\n### Top-Level App Definition\n\n```rust\nuse makepad_widgets::*;\n\nlive_design! {\n    use link::theme::*;\n    use link::widgets::*;\n\n    App = {{App}} {\n        ui: <Root>{\n            main_window = <Window> {\n                window: {inner_size: vec2(1280, 800), title: \"MyApp\"},\n                body = {\n                    // Main content here\n                }\n            }\n        }\n    }\n}\n\napp_main!(App);\n\n#[derive(Live)]\npub struct App {\n    #[live] ui: WidgetRef,\n    #[rust] app_state: AppState,\n}\n\nimpl LiveRegister for App {\n    fn live_register(cx: &mut Cx) {\n        // Order matters: register base widgets first\n        makepad_widgets::live_design(cx);\n        // Then shared/common widgets\n        crate::shared::live_design(cx);\n        // Then feature modules\n        crate::home::live_design(cx);\n    }\n}\n\nimpl LiveHook for App {\n    fn after_new_from_doc(&mut self, cx: &mut Cx) {\n        // One-time initialization after widget tree is created\n    }\n}\n```\n\n### AppMain Implementation\n\n```rust\nimpl AppMain for App {\n    fn handle_event(&mut self, cx: &mut Cx, event: &Event) {\n        // Forward to MatchEvent trait\n        self.match_event(cx, event);\n\n        // Pass AppState through widget tree via Scope\n        let scope = &mut Scope::with_data(&mut self.app_state);\n        self.ui.handle_event(cx, event, scope);\n    }\n}\n```\n\n## Tokio Runtime Integration\n\n### Static Runtime Initialization\n\n```rust\nuse std::sync::Mutex;\nuse tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender};\n\nstatic TOKIO_RUNTIME: Mutex<Option<tokio::runtime::Runtime>> = Mutex::new(None);\nstatic REQUEST_SENDER: Mutex<Option<UnboundedSender<AppRequest>>> = Mutex::new(None);\n\npub fn start_async_runtime() -> Result<tokio::runtime::Handle> {\n    let (request_sender, request_receiver) = tokio::sync::mpsc::unbounded_channel();\n\n    let rt_handle = TOKIO_RUNTIME.lock().unwrap()\n        .get_or_insert_with(|| {\n            tokio::runtime::Runtime::new()\n                .expect(\"Failed to create Tokio runtime\")\n        })\n        .handle()\n        .clone();\n\n    // Store sender for UI thread to use\n    *REQUEST_SENDER.lock().unwrap() = Some(request_sender);\n\n    // Spawn the main worker task\n    rt_handle.spawn(worker_task(request_receiver));\n\n    Ok(rt_handle)\n}\n```\n\n### Request Submission Pattern\n\n```rust\npub enum AppRequest {\n    FetchData { id: String },\n    SendMessage { content: String },\n    // ... other request types\n}\n\n/// Submit a request from UI thread to async runtime\npub fn submit_async_request(req: AppRequest) {\n    if let Some(sender) = REQUEST_SENDER.lock().unwrap().as_ref() {\n        sender.send(req)\n            .expect(\"BUG: worker task receiver has died!\");\n    }\n}\n```\n\n### Worker Task Pattern\n\n```rust\nasync fn worker_task(mut request_receiver: UnboundedReceiver<AppRequest>) -> Result<()> {\n    while let Some(request) = request_receiver.recv().await {\n        match request {\n            AppRequest::FetchData { id } => {\n                // Spawn a new task for each request\n                let _task = tokio::spawn(async move {\n                    let result = fetch_data(&id).await;\n                    // Post result back to UI thread\n                    Cx::post_action(DataFetchedAction { id, result });\n                });\n            }\n            AppRequest::SendMessage { content } => {\n                let _task = tokio::spawn(async move {\n                    match send_message(&content).await {\n                        Ok(()) => Cx::post_action(MessageSentAction::Success),\n                        Err(e) => Cx::post_action(MessageSentAction::Failed(e)),\n                    }\n                });\n            }\n        }\n    }\n    Ok(())\n}\n```\n\n## Lock-Free Update Queue Pattern\n\nFor high-frequency updates from background tasks:\n\n```rust\nuse crossbeam_queue::SegQueue;\nuse makepad_widgets::SignalToUI;\n\npub enum DataUpdate {\n    NewItem { item: Item },\n    ItemChanged { id: String, changes: Changes },\n    Status { message: String },\n}\n\nstatic PENDING_UPDATES: SegQueue<DataUpdate> = SegQueue::new();\n\n/// Called from background async tasks\npub fn enqueue_update(update: DataUpdate) {\n    PENDING_UPDATES.push(update);\n    SignalToUI::set_ui_signal();  // Wake UI thread\n}\n\n// In widget's handle_event:\nimpl Widget for MyWidget {\n    fn handle_event(&mut self, cx: &mut Cx, event: &Event, scope: &mut Scope) {\n        // Poll for updates on Signal events\n        if let Event::Signal = event {\n            while let Some(update) = PENDING_UPDATES.pop() {\n                match update {\n                    DataUpdate::NewItem { item } => {\n                        self.items.push(item);\n                        self.redraw(cx);\n                    }\n                    // ... handle other updates\n                }\n            }\n        }\n    }\n}\n```\n\n## Startup Sequence\n\n```rust\nimpl MatchEvent for App {\n    fn handle_startup(&mut self, cx: &mut Cx) {\n        // 1. Initialize logging\n        let _ = tracing_subscriber::fmt::try_init();\n\n        // 2. Initialize app data directory\n        let _app_data_dir = crate::app_data_dir();\n\n        // 3. Load persisted state\n        if let Err(e) = persistence::load_window_state(\n            self.ui.window(ids!(main_window)), cx\n        ) {\n            error!(\"Failed to load window state: {}\", e);\n        }\n\n        // 4. Update UI based on loaded state\n        self.update_ui_visibility(cx);\n\n        // 5. Start async runtime\n        let _rt_handle = crate::start_async_runtime().unwrap();\n    }\n}\n```\n\n## Shutdown Sequence\n\n```rust\nimpl AppMain for App {\n    fn handle_event(&mut self, cx: &mut Cx, event: &Event) {\n        if let Event::Shutdown = event {\n            // Save window geometry\n            let window_ref = self.ui.window(ids!(main_window));\n            if let Err(e) = persistence::save_window_state(window_ref, cx) {\n                error!(\"Failed to save window state: {e}\");\n            }\n\n            // Save app state\n            if let Some(user_id) = current_user_id() {\n                if let Err(e) = persistence::save_app_state(\n                    self.app_state.clone(), user_id\n                ) {\n                    error!(\"Failed to save app state: {e}\");\n                }\n            }\n        }\n        // ... rest of event handling\n    }\n}\n```\n\n## Best Practices\n\n1. **Separation of Concerns**: Keep UI logic on the main thread, async operations in Tokio runtime\n2. **Request/Response Pattern**: Use typed enums for requests and actions\n3. **Lock-Free Updates**: Use `crossbeam::SegQueue` for high-frequency background updates\n4. **SignalToUI**: Always call `SignalToUI::set_ui_signal()` after enqueueing updates\n5. **Cx::post_action()**: Use for async task results that need action handling\n6. **Scope::with_data()**: Pass shared state through widget tree\n7. **Module Registration Order**: Register base widgets before dependent modules in `live_register()`\n\n## Reference Files\n\n- `references/tokio-integration.md` - Detailed Tokio runtime patterns (Robrix)\n- `references/channel-patterns.md` - Channel communication patterns (Robrix)\n- `references/moly-async-patterns.md` - Cross-platform async patterns (Moly)\n  - `PlatformSend` trait for native/WASM compatibility\n  - `UiRunner` for async defer operations\n  - `AbortOnDropHandle` for task cancellation\n  - `ThreadToken` for non-Send types on WASM\n  - `spawn()` platform-agnostic function\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.","tags":["robius","app","architecture","antigravity","awesome","skills","sickn33","agent-skills","agentic-skills","ai-agent-skills","ai-agents","ai-coding"],"capabilities":["skill","source-sickn33","skill-robius-app-architecture","topic-agent-skills","topic-agentic-skills","topic-ai-agent-skills","topic-ai-agents","topic-ai-coding","topic-ai-workflows","topic-antigravity","topic-antigravity-skills","topic-claude-code","topic-claude-code-skills","topic-codex-cli","topic-codex-skills"],"categories":["antigravity-awesome-skills"],"synonyms":[],"warnings":[],"endpointUrl":"https://skills.sh/sickn33/antigravity-awesome-skills/robius-app-architecture","protocol":"skill","transport":"skills-sh","auth":{"type":"none","details":{"cli":"npx skills add sickn33/antigravity-awesome-skills","source_repo":"https://github.com/sickn33/antigravity-awesome-skills","install_from":"skills.sh"}},"qualityScore":"0.700","qualityRationale":"deterministic score 0.70 from registry signals: · indexed on github topic:agent-skills · 34583 github stars · SKILL.md body (10,731 chars)","verified":false,"liveness":"unknown","lastLivenessCheck":null,"agentReviews":{"count":0,"score_avg":null,"cost_usd_avg":null,"success_rate":null,"latency_p50_ms":null,"narrative_summary":null,"summary_updated_at":null},"enrichmentModel":"deterministic:skill-github:v1","enrichmentVersion":1,"enrichedAt":"2026-04-22T18:52:08.871Z","embedding":null,"createdAt":"2026-04-18T21:43:38.452Z","updatedAt":"2026-04-22T18:52:08.871Z","lastSeenAt":"2026-04-22T18:52:08.871Z","tsv":"'08':131 '09':141 '1':750,904 '1280':264 '13':149 '2':759,920 '3':772,930 '4':796,944 '5':807,955 '6':968 '7':978 '800':265 'abortondrophandl':1021 'action':24,207,212,589,610,617,929,958,966 'agnost':1036 'ai':69 'alway':946 'api':79 'app':3,9,27,33,111,163,236,241,255,256,272,274,279,284,290,327,353,741,761,765,769,825,870,886,895 'applic':41,50,71,90,106 'appmain':347,351,823 'apprequest':495,520,559,593 'appstat':286,373 'architectur':4,10,34,158 'ask':1071 'async':15,17,92,113,123,133,135,174,192,202,430,512,517,542,573,600,668,809,816,915,961,1008,1018 'async-load':132 'await':556,580,606 'back':208,583 'backend':93 'background':66,634,667,942 'base':42,127,300,799,983 'best':36,902 'bodi':268 'boundari':1079 'bug':532 'build':87 'built':51 'call':228,665,947 'cancel':1024 'chang':654,655 'channel':181,227,442,1000 'chat':61,70 'clarif':1073 'clear':1046 'client':62 'clone':463 'codebas':48,58 'communic':97,1001 'compat':1015 'complex':63 'concern':907 'content':270,500,595,605 'core':157 'crate':311,319,768,814 'creat':346,459 'criteria':1082 'critic':5 'cross':73,1006 'cross-platform':72,1005 'crossbeam':178,226,638,936 'current':877 'cx':22,210,294,296,307,315,323,335,337,359,361,370,390,587,608,615,699,701,731,747,749,788,806,831,833,861,956 'data':136,172,221,384,578,762,766,770,971 'datafetchedact':590 'dataupd':647,675,725 'defer':1019 'definit':242 'depend':986 'deriv':275 'describ':1050 'descript':130 'design':95,248,306,314,322 'detail':994 'die':537 'dir':767,771 'directori':128,763 'doc':332 'e':614,620,779,795,854,868,883,897 'enqueu':672,953 'enum':494,646,925 'environ':1062 'environment-specif':1061 'err':613,778,853,882 'error':789,862,891 'event':356,362,363,369,371,389,391,689,696,702,703,712,715,717,828,834,835,838,840,900 'expect':456,531 'expert':1067 'extern':220 'fail':457,619,790,863,892 'featur':317 'fetch':577 'fetchdata':496,560 'file':992 'first':302 'fmt':756 'fn':291,328,354,428,515,543,671,694,742,826 'forward':364 'framework':56 'free':184,624,933 'frequenc':631,941 'full':153 'function':1037 'geometri':843 'get':448 'handl':30,355,445,462,488,688,695,732,743,813,827,901,967 'handler':196 'high':630,940 'high-frequ':629,939 'home':320 'id':497,561,579,591,652,785,848,876,879,890 'impl':287,324,350,690,738,822 'implement':348 'increment':145 'init':758 'initi':341,398,751,760 'inner':261 'input':1076 'insert':450 'integr':94,152,156,395 'item':215,649,650,727,729 'itemchang':651 'keep':908 'keyword':107 'let':379,433,443,522,552,569,575,596,714,719,753,764,777,811,837,844,852,873,881 'level':240 'limit':1038 'link':250,253 'listen':218 'live':247,276,280,292,305,313,321,989 'livehook':325 'liveregist':288 'load':134,137,139,773,781,792,801 'lock':183,623,932 'lock-fre':182,622,931 'log':752 'logic':910 'main':258,269,273,478,786,849,913 'makepad':40,53,89,100,110,114,116,162,245,303,642 'match':557,602,723,1047 'matchev':29,366,739 'matrix':60 'matter':298 'messag':604,657 'messagesentact':611,618 'miss':1084 'modul':318,979,987 'moli':47,68,1010 'move':574,601 'mpsc':180,407,440 'mut':295,333,336,357,360,381,385,546,697,700,705,745,748,829,832 'mutex':403,413,415,421,424 'myapp':267 'mywidget':693 'nativ':75 'native/wasm':1014 'need':965 'new':330,416,425,455,564,664 'newitem':648,726 'non':1028 'non-send':1027 'none':417,426 'ok':486,607,621 'one':339 'one-tim':338 'oper':916,1020 'option':414,422 'order':297,981 'output':1056 'pass':372,972 'pattern':11,98,118,124,129,159,491,540,627,922,997,1002,1009 'pend':660 'pending_updates.pop':722 'pending_updates.push':676 'per':204,214 'per-item':213 'permiss':1077 'persist':774,780,855,884 'platform':74,1007,1035 'platform-agnost':1034 'platformsend':1011 'poll':707 'post':23,206,211,581,588,609,616,957 'practic':37,903 'product':49,117,121 'production-readi':120 'pub':277,427,493,514,645,670 'queue':626,639 'readi':122 'receiv':197,437,485,535,548 'ref':528,846,860 'refer':991 'references/channel-patterns.md':999 'references/moly-async-patterns.md':1004 'references/tokio-integration.md':993 'regist':293,299,982,990 'registr':980 'req':519,530 'request':18,175,176,195,198,205,419,434,436,474,484,489,503,507,518,547,554,558,568,927 'request/response':921 'request_receiver.recv':555 'request_sender.lock':471,525 'requir':1075 'rest':898 'result':144,146,432,550,576,582,592,963 'review':1068 'robius':2,8,32,55,104,109 'robius-app-architectur':1 'robius-styl':103 'robrix':45,59,108,998,1003 'rt':444,487,812 'rt_handle.spawn':481 'runtim':155,191,394,397,412,431,453,454,461,513,810,817,919,996 'rust':243,283,349,399,492,541,636,737,821 'safeti':1078 'save':841,856,865,869,885,894 'scope':170,378,380,382,392,704,706,969,1049 'see':125 'segqueu':179,640,662,663,937 'self':334,358,698,746,830 'self.app':386 'self.app_state.clone':888 'self.items.push':728 'self.match':368 'self.redraw':730 'self.ui.handle':388 'self.ui.window':784,847 'self.update':803 'send':223,603,1029 'sender':177,420,435,465,475,524 'sender.send':529 'sendmessag':499,594 'separ':905 'sequenc':736,820 'set':187,230,679,949 'share':312,973 'shared/common':309 'shutdown':819,839 'signal':189,232,681,711,716,951 'signaltoui':21,148,186,229,644,678,945,948 'size':262 'skill':35,85,1041 'skill-robius-app-architecture' 'sourc':57 'source-sickn33' 'spawn':201,476,562,572,599,1033 'specif':1063 'start':429,808,815 'startup':31,735,744 'state':140,168,285,387,775,783,794,802,858,867,871,887,896,974 'static':396,410,418,659 'status':656 'std':401 'stop':1069 'store':464 'stream':78,143,222 'streaming-result':142 'string':498,501,653,658 'struct':278 'structur':28,39,101,112,237 'style':105 'submiss':490 'submit':16,173,505,516 'subscrib':216,755 'subscript':67 'substitut':1059 'success':612,1081 'sync':402,406,439 'sync/async':64,96 'task':26,194,203,217,480,483,534,539,545,565,570,597,635,669,962,1023,1045 'test':1065 'theme':251 'thread':161,468,510,586,684,914 'threadtoken':1025 'time':340 'titl':266 'tokio':14,115,151,154,190,393,405,411,438,452,460,571,598,918,995 'tokio-integr':150 'tokio_runtime.lock':446 'top':239 'top-level':238 'topic-agent-skills' 'topic-agentic-skills' 'topic-ai-agent-skills' 'topic-ai-agents' 'topic-ai-coding' 'topic-ai-workflows' 'topic-antigravity' 'topic-antigravity-skills' 'topic-claude-code' 'topic-claude-code-skills' 'topic-codex-cli' 'topic-codex-skills' 'trace':754 'trait':367,1012 'treat':1054 'tree':166,344,376,977 'tri':757 'trigger':12 'type':504,924,1030 'ui':160,169,188,200,231,235,257,281,467,509,585,680,683,798,804,909,950 'uirunn':1016 'unbound':441 'unboundedreceiv':408,549 'unboundedsend':409,423 'unwrap':447,472,526,818 'updat':185,224,625,632,661,673,674,677,709,721,724,734,797,934,943,954 'use':6,82,83,244,249,252,400,404,470,637,641,923,935,959,1039 'user':875,878,889 'valid':1064 'vec2':263 'via':209,225,377 'view':167 'visibl':805 'wake':234,682 'wasm':76,1032 'widget':165,246,254,301,304,310,343,375,643,686,691,976,984 'widgetref':164,282 'window':259,260,782,787,793,842,845,850,857,859,866 'worker':25,193,479,482,533,538,544 '异步':19 '架构':20","prices":[{"id":"8b940dc2-b23d-4cb1-95b5-c0da3ad8100d","listingId":"412f800e-d519-4fbb-b804-5867b096f096","amountUsd":"0","unit":"free","nativeCurrency":null,"nativeAmount":null,"chain":null,"payTo":null,"paymentMethod":"skill-free","isPrimary":true,"details":{"org":"sickn33","category":"antigravity-awesome-skills","install_from":"skills.sh"},"createdAt":"2026-04-18T21:43:38.452Z"}],"sources":[{"listingId":"412f800e-d519-4fbb-b804-5867b096f096","source":"github","sourceId":"sickn33/antigravity-awesome-skills/robius-app-architecture","sourceUrl":"https://github.com/sickn33/antigravity-awesome-skills/tree/main/skills/robius-app-architecture","isPrimary":false,"firstSeenAt":"2026-04-18T21:43:38.452Z","lastSeenAt":"2026-04-22T18:52:08.871Z"}],"details":{"listingId":"412f800e-d519-4fbb-b804-5867b096f096","quickStartSnippet":null,"exampleRequest":null,"exampleResponse":null,"schema":null,"openapiUrl":null,"agentsTxtUrl":null,"citations":[],"useCases":[],"bestFor":[],"notFor":[],"kindDetails":{"org":"sickn33","slug":"robius-app-architecture","github":{"repo":"sickn33/antigravity-awesome-skills","stars":34583,"topics":["agent-skills","agentic-skills","ai-agent-skills","ai-agents","ai-coding","ai-workflows","antigravity","antigravity-skills","claude-code","claude-code-skills","codex-cli","codex-skills","cursor","cursor-skills","developer-tools","gemini-cli","gemini-skills","kiro","mcp","skill-library"],"license":"mit","html_url":"https://github.com/sickn33/antigravity-awesome-skills","pushed_at":"2026-04-22T06:40:00Z","description":"Installable GitHub library of 1,400+ agentic skills for Claude Code, Cursor, Codex CLI, Gemini CLI, Antigravity, and more. Includes installer CLI, bundles, workflows, and official/community skill collections.","skill_md_sha":"a16fb3b8957629a27f8b514fe7b5962d74820088","skill_md_path":"skills/robius-app-architecture/SKILL.md","default_branch":"main","skill_tree_url":"https://github.com/sickn33/antigravity-awesome-skills/tree/main/skills/robius-app-architecture"},"layout":"multi","source":"github","category":"antigravity-awesome-skills","frontmatter":{"name":"robius-app-architecture","description":"CRITICAL: Use for Robius app architecture patterns. Triggers on:\nTokio, async, submit_async_request, 异步, 架构,\nSignalToUI, Cx::post_action, worker task,\napp structure, MatchEvent, handle_startup"},"skills_sh_url":"https://skills.sh/sickn33/antigravity-awesome-skills/robius-app-architecture"},"updatedAt":"2026-04-22T18:52:08.871Z"}}