원클릭으로
ribir-style-and-cleanliness
// Specialized for code style and cleanliness within the Ribir UI framework. Use when working with Ribir DSL (@, rdl!, pipe!), state management ($read, $write, part_writer), or performance optimizations.
// Specialized for code style and cleanliness within the Ribir UI framework. Use when working with Ribir DSL (@, rdl!, pipe!), state management ($read, $write, part_writer), or performance optimizations.
Specialized guide for debugging Ribir applications using the HTTP Debug Tool API. Use this skill when asked to debug, inspect, or interact with running Ribir applications.
Specialized guide for creating new Ribir UI widgets. Use this skill when asked to create, implement, or refactor a widget in the Ribir framework, especially when the work should include a gallery showcase or a hands-on UI walkthrough.
| name | ribir-style-and-cleanliness |
| description | Specialized for code style and cleanliness within the Ribir UI framework. Use when working with Ribir DSL (@, rdl!, pipe!), state management ($read, $write, part_writer), or performance optimizations. |
This guide ensures that all Ribir code generated by agents meets expert-level standards for readability, performance, and maintainability.
@ SymbolWithin rdl! and related macros (e.g., fn_widget!, widget!), prioritize using @ to declare widgets instead of the raw rdl! syntax.
@Flex { ... }rdl! { Flex { ... } }$ / @ Syntax Boundary$read(s), $write(s), and @Widget { ... } are only valid inside Ribir macro context. Standard Rust code cannot use them directly, even inside closures or helper functions unless those expressions are written inside fn_widget!, rdl!, widget!, or another Ribir DSL macro.
Only the $ family and @ syntax have this boundary. Ordinary macros, function-style helpers such as fn_widget!, button!, text!, flex!, container!, and APIs such as part_writer can be used wherever normal Rust code allows them.
For simple one-line child calls or macro invocations, prefer removing unnecessary braces:
@ { my_widget() } -> @my_widget()
@ { some_macro!(...) } -> @some_macro!(...)
Do not apply this rule to a bare name or single variable such as @ { child }.
Keep @ { ... } when the expression is multi-line, contains local bindings or control flow, is a bare name, or is clearer with an explicit block.
Avoid Borrow Conflicts: Never mix $read and $write on the same state within a single expression (this causes a Rust RefCell runtime panic).
Do Not Assume Closure Context Is Enough: move |_| ... is still plain Rust unless the closure appears inside a Ribir macro body.
move CaptureAlways explicitly use the move keyword in all event closures (e.g., on_tap, on_pointer_move).
on_tap: move |_| ...on_tap: |_| ... (leads to lifetime conflicts)Within Ribir macros, prioritize the automatic capture mechanisms provided by the framework. Avoid manual calls to .clone_writer(), .clone_reader(), or manually defining clone variables outside the macro.
The macros automatically handle capture and lifecycle for:
Read/Write: $read(s), $write(s), $writer(s)
Subscription/Observation: $reader(s), $watcher(s)
Reference Cloning: $clone(s)
✅ Recommended (Idiomatic: Concise & Declarative):
button! {
on_tap: move |_| *$write(cnt) += 1,
@ { pipe!($read(cnt).to_string()) }
}
❌ Avoid (Non-idiomatic: Verbose boilerplate):
let c_cnt = cnt.clone_writer(); // Avoid manual cloning
button! {
on_tap: move |_| *c_cnt.write() += 1,
@ { pipe!(...) }
}
Prefer the most direct widget function macro when a helper function returns a single root widget tree. Use button!, text!, flex!, container!, and similar helpers when they make the root flatter. Most common widgets already have a corresponding function macro; the list in this guide is illustrative, not exhaustive.
Inside an existing DSL tree, prefer normal child declarations such as @FilledButton { ... } instead of switching to @some_macro! just because a function macro exists.
Use fn_widget! only when you need a small DSL scope for local bindings, helper values, or $-based reads around the root tree.
When a function macro name conflicts with another macro in scope, qualify it explicitly.
fn title() -> Widget<'static> {
flex! {
direction: Direction::Horizontal,
align_items: Align::Center,
@Text { text: "Hello" }
@pipe!($read(cnt).to_string())
}
.into_widget()
}
@FilledButton {
on_tap: move |_| submit(),
@ { "Submit" }
}
fn title() -> Widget<'static> {
fn_widget! {
@Flex {
direction: Direction::Horizontal,
align_items: Align::Center,
@Text { text: "Hello" }
}
}
.into_widget()
}
@filled_button! {
on_tap: move |_| submit(),
@ { "Submit" }
}
flex!, stack!, and similar layout macros are the preferred form when a widget can be expressed as a single root layout tree.
Use fn_widget! only when you need Rust logic before the tree description, such as local bindings, control flow, computed values, or state setup.
fn title() -> Widget<'static> {
flex! {
direction: Direction::Horizontal,
@Text { text: "Title" }
}
.into_widget()
}
fn_widget! without a reason
fn title() -> Widget<'static> {
fn_widget! {
@Flex {
direction: Direction::Horizontal,
@Text { text: "Title" }
}
}
.into_widget()
}
DSL should only describe UI tree structure and declarative property bindings. If logic does not involve specific DSL transformations (like $read, pipe!, @) or is complex, extract it outside the macro as standard Rust code.
When you do need state reads inside DSL, place the read as close as possible to the expression that consumes it instead of hoisting it upward.
If the only local binding exists for one child list or subtree, prefer placing it inside that specific @ { ... } block rather than wrapping the whole widget in fn_widget!.
let display_text = if user.is_logged_in() {
format!("Welcome, {}!", user.name())
} else {
"Please log in.".to_string()
};
text! {
text: display_text,
foreground: Color::BLACK,
}
text! {
text: {
if user.is_logged_in() {
format!("Welcome, {}!", user.name())
} else {
"Please log in.".to_string()
}
},
foreground: Color::BLACK,
}
flex! {
@ {
let rows = $read(this).max_rounds();
(0..rows).map(|row| row_widget(row))
}
}
When dealing with long lists or deep UI trees, consider using part_writer for state slicing. This ensures sub-widgets only depend on the specific data they need, avoiding unnecessary re-renders.
// Sub-widget depends only on this specific item
let task = $writer(this).part_writer(
"task_name".into(),
move |todos| PartMut::new(todos.get_task_mut(id).unwrap()),
);
distinct_pipe! over pipe! unless you specifically need to process duplicate values (helps reduce redundant UI updates).Rc and Arc smart pointers (re-exported from rclite in ribir_algo) instead of std::rc::Rc or std::sync::Arc. They are more lightweight and have better performance as they do not support weak counts.debug_nameFor key interactive widgets or those requiring frequent debugging, adding a debug_name is recommended. This significantly improves efficiency when using MCP debugging tools, allowing direct interaction via stable names (e.g., name:counter_button).
button! {
debug_name: "submit_button",
on_tap: move |_| { ... }
}
Before committing any code changes, the following command sequence must be executed:
cargo +nightly ci check (Compile verification)cargo +nightly ci lint (Static analysis, includes formatting)cargo +nightly ci test (Logic verification)