Install any skill in seconds. Free to start, no credit card required.
Get Started Free →CRITICAL: Use for MolyKit AI chat toolkit. Triggers on: BotClient, OpenAI, SSE streaming, AI chat, molykit, PlatformSend, spawn(), ThreadToken, cross-platform async, Chat widget, Messages, PromptInput, Avatar, LLM
.claude/skills/sickn33-molykit/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 30% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 58% | 0% |
| case-03 | ✗→✓ | ▲ Improved | 42% | 0% |
| case-05 | ✗→✓ | ▲ Improved | 33% | 0% |
| case-06 | ✗→✓ | ▲ Improved | 135% | 0% |
Best practices for building AI chat interfaces with Makepad using MolyKit - a toolkit for cross-platform AI chat applications.
Source codebase: /Users/zhangalex/Work/Projects/FW/robius/moly/moly-kit
Use this skill when:
MolyKit provides:
rust/// Implies Send only on native platforms, not on WASM /// - On native: implemented by types that implement Send /// - On WASM: implemented by ALL types pub trait PlatformSend: PlatformSendInner {} /// Boxed future type for cross-platform use pub type BoxPlatformSendFuture<'a, T> = Pin<Box<dyn PlatformSendFuture<Output = T> + 'a>>; /// Boxed stream type for cross-platform use pub type BoxPlatformSendStream<'a, T> = Pin<Box<dyn PlatformSendStream<Item = T> + 'a>>;
rust/// Runs a future independently /// - Uses tokio on native (requires Send) /// - Uses wasm-bindgen-futures on WASM (no Send required) pub fn spawn(fut: impl PlatformSendFuture<Output = ()> + 'static); // Usage spawn(async move { let result = fetch_data().await; Cx::post_action(DataReady(result)); SignalToUI::set_ui_signal(); });
rust/// Handle that aborts its future when dropped pub struct AbortOnDropHandle(AbortHandle); // Usage - task cancelled when widget dropped #[rust] task_handle: Option<AbortOnDropHandle>, fn start_task(&mut self) { let (future, handle) = abort_on_drop(async move { // async work... }); self.task_handle = Some(handle); spawn(async move { let _ = future.await; }); }
rust/// Store non-Send value in thread-local, access via token pub struct ThreadToken<T: 'static>; impl<T> ThreadToken<T> { pub fn new(value: T) -> Self; pub fn peek<R>(&self, f: impl FnOnce(&T) -> R) -> R; pub fn peek_mut<R>(&self, f: impl FnOnce(&mut T) -> R) -> R; } // Usage - wrap non-Send type for use across Send boundaries let token = ThreadToken::new(non_send_value); spawn(async move { token.peek(|value| { // use value... }); });
rustpub trait BotClient: Send { /// Send message with streamed response fn send( &mut self, bot_id: &BotId, messages: &[Message], tools: &[Tool], ) -> BoxPlatformSendStream<'static, ClientResult<MessageContent>>; /// Get available bots/models fn bots(&self) -> BoxPlatformSendFuture<'static, ClientResult<Vec<Bot>>>; /// Clone for passing around fn clone_box(&self) -> Box<dyn BotClient>; } // Usage let client = OpenAIClient::new("https://api.openai.com/v1".into()); client.set_key("sk-...")?; let context = BotContext::from(client);
rust/// Sharable wrapper with loaded bots for sync UI access pub struct BotContext(Arc<Mutex<InnerBotContext>>); impl BotContext { pub fn load(&mut self) -> BoxPlatformSendFuture<ClientResult<()>>; pub fn bots(&self) -> Vec<Bot>; pub fn get_bot(&self, id: &BotId) -> Option<Bot>; pub fn client(&self) -> Box<dyn BotClient>; } // Usage let mut context = BotContext::from(client); spawn(async move { if let Err(errors) = context.load().await.into_result() { // handle errors } Cx::post_action(BotsLoaded); });
rustpub struct Message { pub from: EntityId, // User, System, Bot(BotId), App pub metadata: MessageMetadata, pub content: MessageContent, } pub struct MessageContent { pub text: String, // Main content (markdown) pub reasoning: String, // AI reasoning/thinking pub citations: Vec<String>, // Source URLs pub attachments: Vec<Attachment>, pub tool_calls: Vec<ToolCall>, pub tool_results: Vec<ToolResult>, } pub struct MessageMetadata { pub is_writing: bool, // Still being streamed pub created_at: DateTime<Utc>, }
rust/// Globally unique bot ID: <len>;<id>@<provider> pub struct BotId(Arc<str>); impl BotId { pub fn new(id: &str, provider: &str) -> Self; pub fn id(&self) -> &str; // provider-local id pub fn provider(&self) -> &str; // provider domain } // Example: BotId::new("gpt-4", "api.openai.com") // -> "5;gpt-4@api.openai.com"
rustlive_design! { pub Slot = {{Slot}} { width: Fill, height: Fit, slot = <View> {} // default content } } // Usage - replace content at runtime let mut slot = widget.slot(id!(content)); if let Some(custom) = client.content_widget(cx, ...) { slot.replace(custom); } else { slot.restore(); // back to default slot.default().as_standard_message_content().set_content(cx, &content); }
rustlive_design! { pub Avatar = {{Avatar}} <View> { grapheme = <RoundedView> { visible: false, label = <Label> { text: "P" } } dependency = <RoundedView> { visible: false, image = <Image> {} } } } impl Widget for Avatar { fn draw_walk(&mut self, cx: &mut Cx2d, ...) -> DrawStep { if let Some(avatar) = &self.avatar { match avatar { Picture::Grapheme(g) => { self.view(id!(grapheme)).set_visible(cx, true); self.view(id!(dependency)).set_visible(cx, false); self.label(id!(label)).set_text(cx, &g); } Picture::Dependency(d) => { self.view(id!(dependency)).set_visible(cx, true); self.view(id!(grapheme)).set_visible(cx, false); self.image(id!(image)).load_image_dep_by_path(cx, d.as_str()); } } } self.deref.draw_walk(cx, scope, walk) } }
rust#[derive(Live, Widget)] pub struct PromptInput { #[deref] deref: CommandTextInput, #[live] pub send_icon: LiveValue, #[live] pub stop_icon: LiveValue, #[rust] pub task: Task, // Send or Stop #[rust] pub interactivity: Interactivity, } impl PromptInput { pub fn submitted(&self, actions: &Actions) -> bool; pub fn reset(&mut self, cx: &mut Cx); pub fn set_send(&mut self); pub fn set_stop(&mut self); pub fn enable(&mut self); pub fn disable(&mut self); }
rust#[derive(Live, Widget)] pub struct Messages { #[deref] deref: View, #[rust] pub messages: Vec<Message>, #[rust] pub bot_context: Option<BotContext>, } impl Messages { pub fn set_messages(&mut self, messages: Vec<Message>, scroll_to_bottom: bool); pub fn scroll_to_bottom(&mut self, cx: &mut Cx, triggered_by_stream: bool); pub fn is_at_bottom(&self) -> bool; }
rustimpl Widget for PromptInput { fn handle_event(&mut self, cx: &mut Cx, event: &Event, scope: &mut Scope) { self.deref.handle_event(cx, event, scope); self.ui_runner().handle(cx, event, scope, self); if self.button(id!(attach)).clicked(event.actions()) { let ui = self.ui_runner(); Attachment::pick_multiple(move |result| match result { Ok(attachments) => { ui.defer_with_redraw(move |me, cx, _| { me.attachment_list_ref().write().attachments.extend(attachments); }); } Err(_) => {} }); } } }
rust/// Parse SSE byte stream into message stream pub fn parse_sse<S, B, E>(s: S) -> impl Stream<Item = Result<String, E>> where S: Stream<Item = Result<B, E>>, B: AsRef<[u8]>, { // Split on "\n\n", extract "data:" content // Filter comments and [DONE] messages } // Usage in BotClient::send fn send(&mut self, ...) -> BoxPlatformSendStream<...> { let stream = stream! { let response = client.post(url).send().await?; let events = parse_sse(response.bytes_stream()); for await event in events { let completion: Completion = serde_json::from_str(&event)?; content.text.push_str(&completion.delta.content); yield ClientResult::new_ok(content.clone()); } }; Box::pin(stream) }
llms.txt - Complete MolyKit API reference| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→pass | 20,930 | 10,367 | -50% | 1 | 1 | 0% | 3,803 | 4,946 | +30% | 0 | 0 | — |
case-02 | fail→pass | 17,249 | 12,763 | -26% | 1 | 1 | 0% | 3,654 | 5,773 | +58% | 0 | 0 | — |
case-03 | fail→pass | 16,703 | 8,077 | -52% | 1 | 1 | 0% | 3,160 | 4,491 | +42% | 0 | 0 | — |
case-04 | pass→pass | 16,434 | 7,404 | -55% | 1 | 1 | 0% | 3,121 | 4,132 | +32% | 0 | 0 | — |
case-05 | fail→pass | 16,244 | 6,451 | -60% | 1 | 1 | 0% | 3,097 | 4,129 | +33% | 0 | 0 | — |
case-06 | fail→pass | 9,021 | 4,318 | -52% | 1 | 1 | 0% | 1,611 | 3,790 | +135% | 0 | 0 | — |
case-07 | fail→pass | 13,356 | 3,957 | -70% | 1 | 1 | 0% | 2,642 | 3,766 | +43% | 0 | 0 | — |
case-08 | fail→pass | 19,105 | 5,369 | -72% | 1 | 1 | 0% | 3,130 | 3,969 | +27% | 0 | 0 | — |
case-09 | pass→pass | 18,902 | 10,667 | -44% | 1 | 1 | 0% | 3,620 | 5,214 | +44% | 0 | 0 | — |
case-10 | fail→pass | 10,881 | 8,030 | -26% | 1 | 1 | 0% | 2,401 | 4,634 | +93% | 0 | 0 | — |
case-11 | fail→pass | 13,290 | 5,741 | -57% | 1 | 1 | 0% | 2,475 | 4,033 | +63% | 0 | 0 | — |
case-12 | pass→pass | 16,576 | 4,664 | -72% | 1 | 1 | 0% | 3,448 | 3,865 | +12% | 0 | 0 | — |
case-13 | fail→pass | 13,644 | 3,625 | -73% | 1 | 1 | 0% | 2,476 | 3,599 | +45% | 0 | 0 | — |
case-14 | fail→pass | 13,201 | 2,458 | -81% | 1 | 1 | 0% | 2,636 | 3,451 | +31% | 0 | 0 | — |
case-15 | fail→pass | 13,545 | 6,850 | -49% | 1 | 1 | 0% | 2,348 | 4,125 | +76% | 0 | 0 | — |
case-16 | fail→pass | 16,171 | 6,736 | -58% | 1 | 1 | 0% | 2,936 | 4,247 | +45% | 0 | 0 | — |
case-17 | fail→pass | 12,660 | 7,864 | -38% | 1 | 1 | 0% | 2,144 | 4,374 | +104% | 0 | 0 | — |
case-18 | fail→pass | 13,524 | 6,640 | -51% | 1 | 1 | 0% | 2,303 | 4,099 | +78% | 0 | 0 | — |
case-19 | pass→pass | 17,249 | 14,021 | -19% | 1 | 1 | 0% | 3,508 | 5,783 | +65% | 0 | 0 | — |
case-20 | pass→pass | 14,134 | 6,135 | -57% | 1 | 1 | 0% | 2,773 | 4,129 | +49% | 0 | 0 | — |
case-21 | pass→pass | 14,125 | 9,946 | -30% | 1 | 1 | 0% | 2,806 | 4,857 | +73% | 0 | 0 | — |
case-22 | fail→pass | 16,116 | 8,918 | -45% | 1 | 1 | 0% | 3,187 | 4,762 | +49% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +73 percentage points is the difference between those two pass rates over the 22 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.