| name | makepad-2.0-troubleshooting |
| description | CRITICAL: Use for Makepad 2.0 troubleshooting and common mistakes. Triggers on:
makepad error, makepad bug, makepad problem, makepad issue, makepad not working,
text invisible, widget not showing, click not working, height zero,
makepad pitfall, makepad gotcha, makepad FAQ, makepad help,
script_mod error, compile error, widget not found, render not updating,
hot reload not working, wasm build error, port conflict, server lock,
IME popup, selection handle, popup window crash,
canvas splash, POST splash loop, 100% CPU, set_visible not working,
on_render empty, event bridge unreliable, float time display,
fn tick not called, on_audio not called, button click through,
常见错误, 问题排查, 故障排除, 不显示, 不工作, 看不见, 热重载, 编译错误
|
Makepad 2.0 Common Pitfalls & Troubleshooting Guide
This skill covers common mistakes when building with Makepad 2.0 and the Splash scripting language. Each pitfall includes:
- What the user sees (symptom)
- Why it happens (root cause)
- How to fix it (correct code)
Reference documents: AGENTS.md, splash.md
Pitfall #1: Container height is 0px -- UI is invisible
Symptom: Your entire UI or a section of it does not appear. The container renders with zero height, making all children invisible.
Root Cause: All View-based containers (View, SolidView, RoundedView, etc.) default to height: Fill. When a Fill container is placed inside a Fit parent (or any context where the available height is determined by children), the height resolves to 0px due to circular dependency: the parent asks the child how tall it is, the child says "as tall as my parent", and the result is zero.
Fix: Always set height: Fit on containers that should shrink-wrap their content.
// WRONG -- height defaults to Fill, resolves to 0px in a Fit context
View{
flow: Down
Label{text: "Hello"}
}
// CORRECT -- height: Fit makes the container wrap its children
View{
height: Fit
flow: Down
Label{text: "Hello"}
}
Rule of thumb: Write height: Fit immediately after the opening brace of every container unless you have a fixed-height parent or you explicitly want height: Fill inside a known fixed-size ancestor.
Exception: Inside a fixed-height parent, height: Fill is valid:
View{
height: 300
View{
height: Fill
Label{text: "I fill the 300px"}
}
}
Pitfall #2: Text invisible on colored background -- missing new_batch
Symptom: You add a Label inside a RoundedView or SolidView with a background color, but the text is invisible. The container appears correctly colored but the text cannot be seen, even though draw_text.color is set to a contrasting color.
Root Cause: Makepad batches draw calls by shader type for GPU performance. All Label widgets using the same text shader get batched into one draw call, and all backgrounds into another. Without new_batch: true, the text draw call may execute before the background draw call, placing the text geometrically behind the opaque background.
Fix: Add new_batch: true to any View-based container that has a visible background (show_bg: true or pre-styled views like SolidView, RoundedView) and contains text children.
// WRONG -- text is drawn behind the background due to batching
RoundedView{
height: Fit
draw_bg.color: #333
Label{text: "Can't see me"}
}
// CORRECT -- new_batch forces background to draw before children's text
RoundedView{
height: Fit
new_batch: true
draw_bg.color: #333
Label{text: "Now visible" draw_text.color: #fff}
}
When you MUST use new_batch: true:
- Any container with
show_bg: true (or pre-styled like SolidView, RoundedView) that contains text
- Hoverable items with background animator -- text disappears on hover without it
- Parent containers of repeated items that each have their own background
Pitfall #3: Named child override does not work -- used : instead of :=
Symptom: You define a template with let and try to override a child property per-instance, but the override is silently ignored. The default text always shows.
Root Cause: In Splash, : creates a static property, while := creates a named/dynamic child that is addressable and overridable. If you declare label: Label{...} (with :), the child has no addressable name and the override path label.text: cannot find it.
Fix: Use := for any child you want to reference or override later.
// WRONG -- static child, override fails silently
let Card = View{
height: Fit
title: Label{text: "default"}
}
Card{title.text: "new text"} // Fails! title is not addressable
// CORRECT -- named child with :=, override works
let Card = View{
height: Fit
title := Label{text: "default"}
}
Card{title.text: "new text"} // Works! title is a named child
Additional rule: Named children inside anonymous containers are UNREACHABLE. Every container in the path from root to child must also be named:
// WRONG -- label is inside an anonymous View, unreachable
let Item = View{
height: Fit
View{
flow: Down
label := Label{text: "default"}
}
}
Item{label.text: "new"} // Fails! No path to label through anonymous View
// CORRECT -- full named path
let Item = View{
height: Fit
texts := View{
flow: Down
label := Label{text: "default"}
}
}
Item{texts.label.text: "new"} // Works! Full dot-path through named containers
Pitfall #4: Hex color with letter 'e' renders wrong or causes parse error
Symptom: A hex color like #2ecc71 causes a cryptic parse error such as expected at least one digit in exponent, or the color renders incorrectly.
Root Cause: The Rust tokenizer inside script_mod!{} interprets a digit followed by e as the start of a scientific notation number (e.g., 2e looks like 2 * 10^...). This breaks parsing of hex colors that contain the letter e adjacent to digits.
Fix: Use the #x prefix for any hex color containing the letter e or E.
// WRONG -- parser reads '2e' as scientific notation exponent
draw_bg.color: #2ecc71
draw_bg.color: #1e1e2e
draw_bg.color: #4466ee
// CORRECT -- #x prefix escapes the hex literal
draw_bg.color: #x2ecc71
draw_bg.color: #x1e1e2e
draw_bg.color: #x4466ee
When is #x NOT needed? Colors without the letter e work fine with plain #:
draw_bg.color: #ff4444 // OK -- no 'e'
draw_bg.color: #44cc44 // OK -- no 'e'
draw_bg.color: #333 // OK -- no 'e'
Pitfall #5: border_radius takes wrong type -- must be float, not Inset
Symptom: Attempting to set per-corner border radii with Inset causes a parse error or silently breaks the layout. The rounded corners do not appear.
Root Cause: Border radius is a single f32 uniform value applied uniformly to all corners. It is NOT an Inset-like struct with per-corner values. Passing an Inset or object silently breaks the entire layout.
CRITICAL: The property name differs by context:
- In Canvas Splash (POST /splash): Use
draw_bg.radius with trailing-dot float
- In script_mod! macro: Use
draw_bg.border_radius
Fix: Use a plain float value with the correct property name.
// WRONG -- border_radius is not an Inset
draw_bg.border_radius: Inset{top_left: 10 top_right: 10}
// WRONG -- not an object
draw_bg.border_radius: {top: 10 bottom: 0}
// CORRECT (Canvas Splash context) -- use draw_bg.radius with trailing dot
draw_bg.radius: 10.
// CORRECT (script_mod! context) -- use draw_bg.border_radius
draw_bg.border_radius: 10.0
// For per-corner radii, use RoundedAllView with a vec4
// (top-left, top-right, right-bottom, left-bottom)
RoundedAllView{
height: Fit
draw_bg.border_radius: vec4(10.0 10.0 0.0 0.0)
}
Pitfall #6: Widget not found from Rust -- registration order wrong
Symptom: At runtime, a widget type is not found or a script error occurs saying a widget is not registered. The app may panic or display nothing.
Root Cause: In Makepad 2.0, widget modules must be registered via script_mod(vm) calls in the correct order. Base widgets must be registered before custom widgets, and custom widgets before the UI that uses them. If the order is wrong, a module tries to use a widget type that has not been registered yet.
Fix: Follow the correct registration order in App::run().
impl App {
fn run(vm: &mut ScriptVm) -> Self {
crate::makepad_widgets::script_mod(vm);
crate::my_custom_widgets::script_mod(vm);
crate::app_ui::script_mod(vm);
App::from_script_mod(vm, self::script_mod)
}
}
Key rule: Widget modules must be registered BEFORE UI modules that use them. Always call lib.rs::script_mod before app_ui::script_mod.
Pitfall #7: Filler clips text -- used alongside width: Fill sibling
Symptom: Text in a horizontal layout is cut off halfway. The text label appears to have only half the available width.
Root Cause: Filler{} is defined as View{width: Fill height: Fill}. When placed next to a sibling that also has width: Fill, both compete for the remaining horizontal space and split it 50/50. The text label only gets half the width and text is clipped.
Fix: Remove Filler{} when a sibling already uses width: Fill. The Fill sibling naturally takes all remaining space, pushing Fit-sized siblings to the edge.
// WRONG -- Filler splits space with Fill sibling, text is clipped
View{
flow: Right height: Fit
Label{width: Fill text: "Long text that gets clipped"}
Filler{}
Button{text: "OK"}
}
// CORRECT -- width: Fill on label pushes button to the right edge
View{
flow: Right height: Fit
Label{width: Fill text: "Long text now has full space"}
Button{text: "OK"}
}
// CORRECT use of Filler -- between Fit-sized siblings
View{
flow: Right height: Fit
Label{text: "left"}
Filler{}
Label{text: "right"}
}
Pitfall #8: Text disappears on hover -- animated View without new_batch
Symptom: A list item or button has hover effects. When you hover over it, the background color changes but the text vanishes completely. Moving the cursor away brings the text back.
Root Cause: The hover animator changes the View's background from transparent (#0000) to an opaque or semi-opaque color. Without new_batch: true, the background and text are in the same draw batch. When the background becomes opaque, it covers the text that was drawn in the same batch order.
Fix: Add new_batch: true to any View with show_bg: true that has a hover animator and contains text.
// WRONG -- text disappears when hover activates the background
View{
width: Fill height: Fit
show_bg: true
draw_bg +: {
color: uniform(#0000)
color_hover: uniform(#fff2)
hover: instance(0.0)
pixel: fn(){
return Pal.premul(self.color.mix(self.color_hover, self.hover))
}
}
animator: Animator{
hover: {
default: @off
off: AnimatorState{
from: {all: Forward {duration: 0.15}}
apply: {draw_bg: {hover: 0.0}}
}
on: AnimatorState{
from: {all: Forward {duration: 0.15}}
apply: {draw_bg: {hover: 1.0}}
}
}
}
Label{text: "Vanishes on hover!" draw_text.color: #fff}
}
// CORRECT -- add new_batch: true
View{
width: Fill height: Fit
new_batch: true
show_bg: true
draw_bg +: {
color: uniform(#0000)
color_hover: uniform(#fff2)
hover: instance(0.0)
pixel: fn(){
return Pal.premul(self.color.mix(self.color_hover, self.hover))
}
}
animator: Animator{
hover: {
default: @off
off: AnimatorState{
from: {all: Forward {duration: 0.15}}
apply: {draw_bg: {hover: 0.0}}
}
on: AnimatorState{
from: {all: Forward {duration: 0.15}}
apply: {draw_bg: {hover: 1.0}}
}
}
}
Label{text: "Stays visible on hover!" draw_text.color: #fff}
}
Pitfall #9: Commas in Splash -- tolerated but not required
Symptom: Confusion about whether commas are allowed between properties in script_mod! blocks.
Root Cause: Splash is primarily whitespace-delimited. However, the Splash tokenizer treats commas as whitespace — they are silently consumed and do not cause parse errors. Many Makepad projects (including Robrix) use commas extensively in script_mod! blocks, inherited from Makepad 1.x live_design! syntax which required commas.
Guidance: Both styles are valid. Match the surrounding code style:
// Style A: with commas (common in codebases migrated from 1.x)
View{
flow: Down,
height: Fit,
spacing: 10,
padding: Inset{top: 5, bottom: 5, left: 10, right: 10}
}
// Style B: without commas (pure Splash style)
View{
flow: Down
height: Fit
spacing: 10
padding: Inset{top: 5 bottom: 5 left: 10 right: 10}
}
Note: Both compile and run identically. The tokenizer discards commas. Do NOT waste time removing commas from existing code — it creates noisy diffs with no functional change.
Pitfall #10: Using semicolons -- not valid in Splash
Symptom: Parse errors or unexpected behavior. The semicolons are treated as part of property values or cause tokenizer failures.
Root Cause: Splash does not use semicolons to terminate statements. This is a common mistake for developers coming from CSS, JavaScript, or Rust backgrounds.
Fix: Remove all semicolons.
// WRONG -- semicolons are not valid
View{
flow: Down;
height: Fit;
Label{text: "Hello";};
}
// CORRECT -- no semicolons needed
View{
flow: Down
height: Fit
Label{text: "Hello"}
}
Pitfall #11: Root container without width: Fill -- narrow or broken layout
Symptom: The UI appears as a narrow sliver on one side of the window, or the layout is entirely broken. Content does not fill the available window width.
Root Cause: Using a fixed pixel width (e.g., width: 400) on the outermost container means it does not adapt to the available window space. If the window is wider, the content is a small strip. If narrower, content is clipped.
Fix: Always use width: Fill on the root container. Fixed pixel widths are fine for inner elements.
// WRONG -- fixed width on root, does not adapt to window
RoundedView{
width: 400
height: Fit
flow: Down