- 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()`.
```rust
impl App {
fn run(vm: &mut ScriptVm) -> Self {
// 1. Register base widget library (theme + all standard widgets)
crate::makepad_widgets::script_mod(vm);
// 2. Register your custom widget modules (if any)
crate::my_custom_widgets::script_mod(vm);
// 3. Register your app UI module (uses widgets from steps 1 and 2)
crate::app_ui::script_mod(vm);
// 4. Create the app from its own script_mod
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
عرض على GitHub