1
0
Source

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

Install

mkdir -p .claude/skills/molykit && curl -L -o skill.zip "https://mcp.directory/api/skills/download/4264" && unzip -o skill.zip -d .claude/skills/molykit && rm skill.zip

Installs to .claude/skills/molykit

About this skill

MolyKit Skill

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

Triggers

Use this skill when:

  • Building AI chat interfaces with Makepad
  • Integrating OpenAI or other LLM APIs
  • Implementing cross-platform async for native and WASM
  • Creating chat widgets (messages, prompts, avatars)
  • Handling SSE streaming responses
  • Keywords: molykit, moly-kit, ai chat, bot client, openai makepad, chat widget, sse streaming

Overview

MolyKit provides:

  • Cross-platform async utilities (PlatformSend, spawn(), ThreadToken)
  • Ready-to-use chat widgets (Chat, Messages, PromptInput, Avatar)
  • BotClient trait for AI provider integration
  • OpenAI-compatible client with SSE streaming
  • Protocol types for messages, bots, and tool calls
  • MCP (Model Context Protocol) support

Cross-Platform Async Patterns

PlatformSend - Send Only on Native

/// 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>>;

Platform-Agnostic Spawning

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

Task Cancellation with AbortOnDropHandle

/// 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; });
}

ThreadToken for Non-Send Types on WASM

/// 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...
    });
});

BotClient Trait

Implementing AI Provider Integration

pub 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);

BotContext - Sharable Wrapper

/// 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);
});

Protocol Types

Message Structure

pub 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>,
}

Bot Identification

/// 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"

Widget Patterns

Slot Widget - Runtime Content Replacement

live_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);
}

Avatar Widget - Text/Image Toggle

live_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)
    }
}

PromptInput Widget

#[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);
}

Messages Widget - Conversation View

#[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;
}

UiRunner Pattern for Async-to-UI

impl 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(_) => {}
            });
        }
    }
}

SSE Streaming

/// 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)
}

Best Practices

  1. Use PlatformSend for cross-platform: Same code works on native and WASM
  2. Use spawn() not tokio::spawn: Platform-agnostic task spawning
  3. Use AbortOnDropHandle: Cancel tasks when widget drops
  4. Use ThreadToken for non-Send on WASM: Thread-local storage with token access
  5. Use Slot for custom content: Allow BotClient to provide custom widgets
  6. Use read()/write() pattern: Safe borrow access via WidgetRef
  7. Use UiRunner::defer_with_redraw: Update widget from async context
  8. Handle ClientResult partial success: May have value AND errors

Reference Files

  • llms.txt - Complete MolyKit API reference

makepad-event-action

ZhangHanDong

CRITICAL: Use for Makepad event and action handling. Triggers on: makepad event, makepad action, Event enum, ActionTrait, handle_event, MouseDown, KeyDown, TouchUpdate, Hit, FingerDown, post_action, makepad 事件, makepad action, 事件处理

00

makepad-platform

ZhangHanDong

CRITICAL: Use for Makepad cross-platform support. Triggers on: makepad platform, makepad os, makepad macos, makepad windows, makepad linux, makepad android, makepad ios, makepad web, makepad wasm, makepad metal, makepad d3d11, makepad opengl, makepad webgl, OsType, CxOs, makepad 跨平台, makepad 平台支持

00

makepad-font

ZhangHanDong

CRITICAL: Use for Makepad font and text rendering. Triggers on: makepad font, makepad text, makepad glyph, makepad typography, font atlas, text layout, font family, font size, text shaping, makepad 字体, makepad 文字, makepad 排版, makepad 字形

00

makepad-splash

ZhangHanDong

CRITICAL: Use for Makepad Splash scripting language. Triggers on: splash language, makepad script, makepad scripting, script!, cx.eval, makepad dynamic, makepad AI, splash 语言, makepad 脚本

00

makepad-widgets

ZhangHanDong

CRITICAL: Use for Makepad widgets and UI components. Triggers on: makepad widget, makepad View, makepad Button, makepad Label, makepad Image, makepad TextInput, RoundedView, SolidView, ScrollView, "makepad component", makepad Markdown, makepad Html, TextFlow, rich text, 富文本, markdown渲染, makepad 组件, makepad 按钮, makepad 列表, makepad 视图, makepad 输入框

00

makepad-shaders

ZhangHanDong

CRITICAL: Use for Makepad shader system. Triggers on: makepad shader, makepad draw_bg, Sdf2d, makepad pixel, makepad glsl, makepad sdf, draw_quad, makepad gpu, makepad 着色器, makepad shader 语法, makepad 绘制

00

You might also like

flutter-development

aj-geddes

Build beautiful cross-platform mobile apps with Flutter and Dart. Covers widgets, state management with Provider/BLoC, navigation, API integration, and material design.

641968

drawio-diagrams-enhanced

jgtolentino

Create professional draw.io (diagrams.net) diagrams in XML format (.drawio files) with integrated PMP/PMBOK methodologies, extensive visual asset libraries, and industry-standard professional templates. Use this skill when users ask to create flowcharts, swimlane diagrams, cross-functional flowcharts, org charts, network diagrams, UML diagrams, BPMN, project management diagrams (WBS, Gantt, PERT, RACI), risk matrices, stakeholder maps, or any other visual diagram in draw.io format. This skill includes access to custom shape libraries for icons, clipart, and professional symbols.

590705

godot

bfollington

This skill should be used when working on Godot Engine projects. It provides specialized knowledge of Godot's file formats (.gd, .tscn, .tres), architecture patterns (component-based, signal-driven, resource-based), common pitfalls, validation tools, code templates, and CLI workflows. The `godot` command is available for running the game, validating scripts, importing resources, and exporting builds. Use this skill for tasks involving Godot game development, debugging scene/resource files, implementing game systems, or creating new Godot components.

339397

ui-ux-pro-max

nextlevelbuilder

"UI/UX design intelligence. 50 styles, 21 palettes, 50 font pairings, 20 charts, 8 stacks (React, Next.js, Vue, Svelte, SwiftUI, React Native, Flutter, Tailwind). Actions: plan, build, create, design, implement, review, fix, improve, optimize, enhance, refactor, check UI/UX code. Projects: website, landing page, dashboard, admin panel, e-commerce, SaaS, portfolio, blog, mobile app, .html, .tsx, .vue, .svelte. Elements: button, modal, navbar, sidebar, card, table, form, chart. Styles: glassmorphism, claymorphism, minimalism, brutalism, neumorphism, bento grid, dark mode, responsive, skeuomorphism, flat design. Topics: color palette, accessibility, animation, layout, typography, font pairing, spacing, hover, shadow, gradient."

318395

nano-banana-pro

garg-aayush

Generate and edit images using Google's Nano Banana Pro (Gemini 3 Pro Image) API. Use when the user asks to generate, create, edit, modify, change, alter, or update images. Also use when user references an existing image file and asks to modify it in any way (e.g., "modify this image", "change the background", "replace X with Y"). Supports both text-to-image generation and image-to-image editing with configurable resolution (1K default, 2K, or 4K for high resolution). DO NOT read the image file first - use this skill directly with the --input-image parameter.

450339

fastapi-templates

wshobson

Create production-ready FastAPI projects with async patterns, dependency injection, and comprehensive error handling. Use when building new FastAPI applications or setting up backend API projects.

304231

Stay ahead of the MCP ecosystem

Get weekly updates on new skills and servers.