# Studio Remote Runbook ## Execution Policy - Visual UI programs must be launched and controlled through the Makepad Studio remote protocol. - Always use release builds for runtime validation, profiling, benchmarks, timing checks, or any performance-sensitive command. Use `--release` unless the user explicitly asks for a debug build. - Do not use mount observation or runnable discovery from the bridge client. The bridge must not claim mount ownership from Studio desktop. - Do not launch UI programs with raw `cargo run`, `cargo makepad`, or ad hoc cargo invocation when a runnable item exists. - Do not use bridge `Cargo` requests to run applications. Only launch apps from runnable items via bridge `RunItem`. - For UI runnable targets, do not prebuild or precheck the app from the shell before launching it in Studio. Let the Studio `RunItem` build be the single build path so Cargo fingerprints, env vars, target dirs, and flags stay identical. - Before starting a new UI run for the same target, send `ClearBuild` for the previous build so Studio stops it and removes its run/log/profiler tabs. - `cargo check` or `cargo build` never counts as UI verification. After changing UI/runtime code, you must clear the old build and start a fresh Studio run before trusting screenshots, widget dumps, or interaction results. - Do not keep inspecting an older already-running app after code changes. Re-run the target and verify against the new `build_id`. - Command-line-only tasks (builds, tests, linting, file ops, grep/ripgrep, etc.) can be run directly in the shell. - Prefer studio remote control for any workflow that needs screenshots, widget queries, clicks, typing, or runtime UI inspection. - Before using Studio protocol tools (`FindInFiles`, `ReadTextRange`, `WidgetTreeDump`, `WidgetQuery`, `Screenshot`, `Click`, `TypeText`, `Return`), always start one persistent Studio remote bridge process and reuse it for the entire interaction. - When adding a new example crate, update both the Cargo workspace and `makepad.splash` so Studio exposes the new example as a runnable item. ## Assumptions - Studio is started manually by the user. - Studio remote target is `ip:port` only (no `http://`, no `ws://`), normally `127.0.0.1:8001`. - Use `127.0.0.1:8002` only if Studio reports fallback because `8001` is occupied. - Keep one persistent studio remote process for the whole interaction. ## Start Studio Remote - Command: - `target/release/cargo-makepad studio --studio=127.0.0.1:8001` - Send newline-delimited JSON requests on stdin. - Read newline-delimited JSON responses on stdout. - Protocol shape is raw `ClientToHub` requests on stdin and filtered `HubToClient` responses on stdout. - Do not send `ObserveMount` from the bridge. It can take `primary` UI ownership for the mount and divert RunView/framebuffer traffic away from Studio desktop. ## Request Protocol (JSON Lines) - `{"ListBuilds":[]}` - `{"ClearBuild":{"build_id":[6]}}` stops a running build and immediately clears its Studio UI tabs; use this before rerunning the same app. - `{"StopBuild":{"build_id":[6]}}` stops/kills a running build but does not clear Studio tabs. - `{"RunItem":{"mount":"makepad","name":"makepad-example-todo"}}` - `{"RunItem":{"mount":"makepad","name":"makepad-example-xr-quest"}}` - `{"FindInFiles":{"mount":"makepad","pattern":"ClientToHub::","is_regex":false,"glob":null,"max_results":200}}` - `{"FindInFiles":{"mount":"makepad","pattern":"ClientToHub::(FindInFiles|ReadTextRange)","is_regex":true,"glob":"**/*.rs","max_results":200}}` - `{"ReadTextRange":{"path":"makepad/studio/backend/src/dispatch.rs","start_line":640,"end_line":720}}` - `{"WidgetTreeDump":{"build_id":[6]}}` - `{"WidgetQuery":{"build_id":[6],"query":"id:todo_input"}}` - `{"Screenshot":{"build_id":[6],"kind_id":0}}` (`kind_id` optional; defaults to `0`) - `{"Click":{"build_id":[6],"x":1274,"y":342}}` - `{"TypeText":{"build_id":[6],"text":"hello"}}` - `{"Return":{"build_id":[6],"auto_dump":false}}` - `{"ForwardToApp":{"build_id":[6],"msg_bin":[...]}}` (advanced; binary payload) ## `StudioToApp` API (Updated) - The studio remote bridge supports raw app event passthrough via `UIToStudio::ForwardToApp`. - Current `StudioToApp` variants include: - `Screenshot`, `WidgetTreeDump`, `KeepAlive`, `LiveChange`, `Swapchain`, `WindowGeomChange`, `Tick` - `MouseDown`, `MouseUp`, `MouseMove`, `Scroll` - `KeyDown`, `KeyUp`, `TextInput`, `TextCopy`, `TextCut` - `None`, `Kill` - Use direct studio remote requests (`Screenshot`, `WidgetTreeDump`, `Click`, `TypeText`, `Return`) for normal automation. - Use raw `StudioToApp` only for low-level event injection/debugging. ## Response Notes (Current) - Bridge stdout is filtered to: `Hello`, `Error`, `TextFileRead`, `TextFileRange`, `FindFileResults`, `SearchFileResults`, `Builds`, `RunItems`, `BuildStarted`, `BuildStopped`, `BuildCleared`, `AppStarted`, `RunViewCreated`, `QueryLogResults`, `Screenshot`, `WidgetTreeDump`, `WidgetQuery`, `QueryCancelled`. - `BuildCleared` is a Studio frontend cleanup signal routed to the primary UI for the build's mount; bridge clients should not wait for it before starting the next run. - `RunViewFrame` and the terminal stream are not exposed by the bridge. - `Screenshot` responses include file metadata (`path`, `width`, `height`) and not inline PNG bytes. - `WidgetTreeDump` responses include text dump content keyed by `request_id`. - `FindInFiles` responds as `SearchFileResults` with concise entries (`path`, `line`, `column`, `line_text`) and `done`. - `FindInFiles` defaults to searching only `.rs`, `.md`, `.toml` files unless `glob` is provided. - `ReadTextRange` responds as `TextFileRange` with `path`, requested `start_line`/`end_line`, `total_lines`, and `content`. - Query-scoped responses are lane-filtered by `query_id.client_id`; only this bridge client's query results are emitted. - Build ids and query ids are `QueryId` tuple structs, so JSON encodes them as one-element arrays like `[6]`. - `FindInFiles`/`SearchFiles` execution is worker-pooled in backend (not main dispatch thread). ## Recommended Control Flow 1. Start studio remote process once. 2. Determine the target runnable item name locally from the repo or from the user request. 3. Call `ListBuilds` and find any existing build for the same runnable item. 4. Send `ClearBuild` for that old `build_id`; do not wait for an acknowledgment before the next launch. 5. Start the new UI app through `RunItem`, and wait for `BuildStarted` and `AppStarted`. 6. After any code change that affects runtime/UI behavior, repeat steps 3-5 before doing screenshots, widget dumps, clicks, or visual conclusions. 7. For code search, use `FindInFiles` first, then `ReadTextRange` to window exact regions. 8. Use direct shell cargo commands for non-launch tasks such as `check`, `build`, `test`, or `bench`. 9. Use `WidgetQuery` / `WidgetTreeDump` to get click targets. 10. For text input, click field first, then send text, then return. 11. Keep control packets compact (`auto_dump:false` on click/type/return for low latency). ## `RunItem` Launch - `RunItem` executes a Studio-defined runnable item by name. - Use the runnable item name shown in Studio, not a Cargo package name. - `RunItem` does not implicitly replace an older build tab; agents should clear the old build themselves first with `ClearBuild`. ## One-Flow Input Burst - Send this as one stdin write (multiple JSON lines, no sleeps): - `Click` (input field center) - `TypeText` - `Return` - Then request `WidgetTreeDump` or `Screenshot` to confirm. ## Coordinates - Use coordinates from dump as-is. - `W3` dump uses integer pixel coordinates in the same space expected by `Click`. - Do not apply extra DPI math in the agent loop. ## Reliability Notes - `Screenshot` can arrive before visible redraw after rapid input bursts. - If screenshot looks stale, request a follow-up `WidgetTreeDump`/`Screenshot`. - If input does nothing: - Verify `build_id` with `ListBuilds`. - Refresh dump and retry click on input before typing. - If request errors with no active websocket: - app is not connected yet; wait for startup completion and retry. ## CLAUDE.md Body The following is the current body of CLAUDE.md included verbatim for agent guidance parity. # Makepad Project Guide ## Important: When Converting Syntax **Always search for existing usage patterns in the NEW crates (widgets, code_editor, studio) before making syntax changes.** The old `widgets` and `live_design!` syntax is deprecated. When unsure about the correct syntax for something, grep for similar usage in `widgets/src/` to find the correct pattern. ```bash # Example: find how texture declarations work in new system grep -r "texture_2d" widgets/src/ ``` **Critical: Always use `Name: value` syntax, never `Name = value`.** The old `Key = Value` syntax no longer works. For named widget instances, use `name := Type{...}` syntax. ## Running UI Programs Use the Studio bridge runnable-item flow instead of launching UI apps directly from the shell: 1. Start the Studio remote bridge once. 2. Determine the runnable item name locally. 3. If an older instance is still running, clear it with `{"ClearBuild":{"build_id":[N]}}` and launch the replacement immediately without waiting for an acknowledgment. 4. Launch it with `{"RunItem":{"mount":"makepad","name":""}}`. 5. After editing UI/runtime code, do not inspect the previously running build. Always verify against the newly started build id from step 4. Do not use `ObserveMount` from the bridge. That call is for mount ownership/subscription and can steal RunView/framebuffer routing away from Studio desktop. Use direct shell cargo commands only for non-UI tasks such as library checks, tests, and file/search operations. Do not run shell `cargo check`, `cargo build`, or `cargo run` for UI runnable targets that will be launched via Studio. When those non-UI tasks are used for runtime behavior or performance measurements, prefer their release variants (`cargo run --release`, `cargo test --release`, `cargo build --release`). ## Cargo.toml Setup ```toml [package] name = "makepad-example-myapp" version = "0.1.0" edition = "2021" [dependencies] makepad-widgets = { path = "../../widgets" } ``` ## Widgets DSL (script_mod!) The new DSL uses `script_mod!` macro with runtime script evaluation instead of the old `live_design!` compile-time macros. ### Imports and App Setup ```rust use makepad_widgets::*; app_main!(App); script_mod!{ use mod.prelude.widgets.* load_all_resources() do #(App::script_component(vm)){ ui: Root{ main_window := Window{ window.inner_size: vec2(800, 600) body +: { // UI content here } } } } } impl App { fn run(vm: &mut ScriptVm) -> Self { crate::makepad_widgets::script_mod(vm); // Register all widgets // Platform-specific initialization goes here (e.g., vm.cx().start_stdin_service() for macos) App::from_script_mod(vm, self::script_mod) } } #[derive(Script, ScriptHook)] pub struct App { #[live] ui: WidgetRef, } impl MatchEvent for App { fn handle_actions(&mut self, cx: &mut Cx, actions: &Actions) { // Handle widget actions } } impl AppMain for App { fn handle_event(&mut self, cx: &mut Cx, event: &Event) { self.match_event(cx, event); self.ui.handle_event(cx, event, &mut Scope::empty()); } } ``` ### Available Widgets (widgets/src/lib.rs) Core: `View`, `SolidView`, `RoundedView`, `ScrollXView`, `ScrollYView`, `ScrollXYView` Text: `Label`, `H1`, `H2`, `H3`, `LinkLabel`, `TextInput` Buttons: `Button`, `ButtonFlat`, `ButtonFlatter` Toggles: `CheckBox`, `Toggle`, `RadioButton` Input: `Slider`, `DropDown` Layout: `Splitter`, `FoldButton`, `FoldHeader`, `Hr` Lists: `PortalList` Navigation: `StackNavigation`, `ExpandablePanel` Overlays: `Modal`, `Tooltip`, `PopupNotification` Dock: `Dock`, `DockSplitter`, `DockTabs`, `DockTab` Media: `Image`, `Icon`, `LoadingSpinner` Special: `FileTree`, `PageFlip`, `CachedWidget` Window: `Window`, `Root` Markup: `Html`, `Markdown` (feature-gated) ### Widget Definition Pattern ```rust // Rust struct #[derive(Script, ScriptHook, Widget)] pub struct MyWidget { #[source] source: ScriptObjectRef, // Required for script integration #[walk] walk: Walk, #[layout] layout: Layout, #[redraw] #[live] draw_bg: DrawQuad, #[live] draw_text: DrawText, #[rust] my_state: i32, // Runtime-only field } // For widgets with animations, add Animator derive: #[derive(Script, ScriptHook, Widget, Animator)] pub struct AnimatedWidget { #[source] source: ScriptObjectRef, #[apply_default] animator: Animator, // ... } ``` ### Script Module Structure ```rust script_mod!{ use mod.prelude.widgets_internal.* // For internal widget definitions use mod.widgets.* // Access other widgets // Register base widget (connects Rust struct to script) mod.widgets.MyWidgetBase = #(MyWidget::register_widget(vm)) // Create styled variant with defaults mod.widgets.MyWidget = set_type_default() do mod.widgets.MyWidgetBase{ width: Fill height: Fit padding: theme.space_2 draw_bg +: { color: theme.color_bg_app } } } ``` ### Key Syntax Differences (Old vs New) | Old (live_design!) | New (script_mod!) | |-------------------|-------------------| | `` | `mod.widgets.BaseWidget{ }` | | `{{StructName}}` | `#(Struct::register_widget(vm))` | | `(THEME_COLOR_X)` | `theme.color_x` | | `` | `theme.font_regular` | | `instance hover: 0.0` | `hover: instance(0.0)` | | `uniform color: #fff` | `color: uniform(#fff)` | | `draw_bg: { }` (replace) | `draw_bg +: { }` (merge) | | `default: off` | `default: @off` | | `fn pixel(self)` | `pixel: fn()` | | `item.apply_over(cx, live!{...})` | `script_apply_eval!(cx, item, {...})` | ### Runtime Property Updates with script_apply_eval! Use `script_apply_eval!` macro to dynamically update widget properties at runtime: ```rust // Old system (live! macro with apply_over) item.apply_over(cx, live!{ height: (height) draw_bg: {is_even: (if is_even {1.0} else {0.0})} }); // New system (script_apply_eval! macro) script_apply_eval!(cx, item, { height: #(height) draw_bg: {is_even: #(if is_even {1.0} else {0.0})} }); // For colors, use #(color) syntax let color = self.color_focus; script_apply_eval!(cx, item, { draw_bg: { color: #(color) } }); ``` Note: In `script_apply_eval!`, use `#(expr)` for Rust expression interpolation instead of `(expr)`. ### Theme Access Always use `theme.` prefix: ```rust color: theme.color_bg_app padding: theme.space_2 font_size: theme.font_size_p text_style: theme.font_regular ``` ### Property Merging with `+:` The `+:` operator merges with parent instead of replacing: ```rust mod.widgets.MyButton = mod.widgets.Button{ draw_bg +: { color: #f00 // Only overrides color, keeps other draw_bg properties } } ``` ### Shader Instance vs Uniform - `instance(value)` - Per-draw-call value (can vary per widget instance) - `uniform(value)` - Shared across all instances using same shader ```rust draw_bg +: { hover: instance(0.0) // Each button has its own hover state color: uniform(theme.color_x) // Shared base color color_hover: instance(theme.color_y) // Per-instance if color varies } ``` ### Animator Definition ```rust animator: Animator{ hover: { default: @off off: AnimatorState{ from: {all: Forward {duration: 0.1}} apply: { draw_bg: {hover: 0.0} draw_text: {hover: 0.0} } } on: AnimatorState{ from: {all: Snap} // Instant transition apply: { draw_bg: {hover: 1.0} draw_text: {hover: 1.0} } } } } ``` ### Shader Functions ```rust draw_bg +: { pixel: fn() { let sdf = Sdf2d.viewport(self.pos * self.rect_size) sdf.box(0.0, 0.0, self.rect_size.x, self.rect_size.y, 4.0) sdf.fill(self.color.mix(self.color_hover, self.hover)) return sdf.result } } ``` Note: Use `.method()` not `::method()` in shaders. ### Color Mixing (Method Chaining) ```rust // Old nested style (avoid) mix(mix(mix(color1, color2, hover), color3, down), color4, focus) // New chained style (preferred) color1.mix(color2, hover).mix(color3, down).mix(color4, focus) ``` ### App Structure Pattern ```rust script_mod!{ use mod.prelude.widgets.* load_all_resources() do #(App::script_component(vm)){ ui: Root{ main_window := Window{ window.inner_size: vec2(1000, 700) body +: { // Your UI here MyWidget{} } } } } } impl App { fn run(vm: &mut ScriptVm) -> Self { crate::makepad_widgets::script_mod(vm); // Platform-specific initialization (e.g., vm.cx().start_stdin_service() for macos) App::from_script_mod(vm, self::script_mod) } } #[derive(Script, ScriptHook)] pub struct App { #[live] ui: WidgetRef, } impl MatchEvent for App { fn handle_actions(&mut self, cx: &mut Cx, actions: &Actions) { if self.ui.button(ids!(my_button)).clicked(actions) { log!("Button clicked!"); } } } impl AppMain for App { fn handle_event(&mut self, cx: &mut Cx, event: &Event) { self.match_event(cx, event); self.ui.handle_event(cx, event, &mut Scope::empty()); } } ``` ### Widget ID References Use `:=` for named widget instances: ```rust // In DSL my_button := Button{text: "Click"} // In Rust code self.ui.button(ids!(my_button)).clicked(actions) ``` ### Template Definitions in Dock Templates inside Dock are local; use `let` bindings at script level for reusable components: ```rust script_mod!{ // Reusable at script level let MyPanel = SolidView{ width: Fill height: Fill // ... } // Use directly body +: { MyPanel{} // Works because it's a let binding } } ``` ### Custom Draw Widget Example ```rust #[derive(Script, ScriptHook, Widget)] pub struct CustomDraw { #[walk] walk: Walk, #[layout] layout: Layout, #[redraw] #[live] draw_quad: DrawQuad, #[rust] area: Area, } impl Widget for CustomDraw { fn draw_walk(&mut self, cx: &mut Cx2d, _scope: &mut Scope, walk: Walk) -> DrawStep { cx.begin_turtle(walk, self.layout); let rect = cx.turtle().rect(); self.draw_quad.draw_abs(cx, rect); cx.end_turtle_with_area(&mut self.area); DrawStep::done() } fn handle_event(&mut self, _cx: &mut Cx, _event: &Event, _scope: &mut Scope) {} } ``` ### Script Object Storage: map vs vec In script objects, properties are stored in two different places: - **`map`**: Contains `key: value` pairs (regular properties) - **`vec`**: Contains named template items (via `:=` syntax) This distinction is important when working with `on_after_apply` or inspecting script objects directly. ### Templates in List Widgets (PortalList, FlatList) In list widgets, named IDs (using `:=`) define **templates** that are stored in the widget's `templates` HashMap. These are NOT regular properties - they go into the script object's vec and are collected via `on_after_apply`. ```rust // In script_mod! - defining templates for a list my_list := PortalList { // Regular properties (go into struct fields) width: Fill height: Fill scroll_bar: mod.widgets.ScrollBar {} // Templates (named with :=) - stored in templates HashMap, NOT struct fields Item := View { height: 40 title := Label { text: "Default" } } Header := View { draw_bg: { color: #333 } } } ``` The templates are collected in `on_after_apply`: ```rust impl ScriptHook for PortalList { fn on_after_apply(&mut self, vm: &mut ScriptVm, apply: &Apply, scope: &mut Scope, value: ScriptValue) { if let Some(obj) = value.as_object() { vm.vec_with(obj, |_vm, vec| { for kv in vec { if let Some(id) = kv.key.as_id() { self.templates.insert(id, kv.value); } } }); } } } ``` Then used during drawing: ```rust while let Some(item_id) = list.next_visible_item(cx) { let item = list.item(cx, item_id, id!(Item)); item.label(ids!(title)).set_text(cx, &format!("Item {}", item_id)); item.draw_all(cx, &mut Scope::empty()); } ``` **Key distinction**: Regular properties like `scroll_bar: mod.widgets.ScrollBar {}` are applied directly to struct fields. Template definitions like `Item := View {...}` are stored separately for dynamic instantiation. ### PortalList Usage ```rust #[derive(Script, ScriptHook, Widget)] pub struct MyList { #[deref] view: View, } impl Widget for MyList { fn draw_walk(&mut self, cx: &mut Cx2d, scope: &mut Scope, walk: Walk) -> DrawStep { while let Some(item) = self.view.draw_walk(cx, scope, walk).step() { if let Some(mut list) = item.borrow_mut::() { list.set_item_range(cx, 0, 100); // 100 items while let Some(item_id) = list.next_visible_item(cx) { let item = list.item(cx, item_id, id!(Item)); item.label(ids!(title)).set_text(cx, &format!("Item {}", item_id)); item.draw_all(cx, &mut Scope::empty()); } } } DrawStep::done() } } ``` ### FileTree Usage ```rust impl Widget for FileTreeDemo { fn draw_walk(&mut self, cx: &mut Cx2d, scope: &mut Scope, walk: Walk) -> DrawStep { while self.file_tree.draw_walk(cx, scope, walk).is_step() { self.file_tree.set_folder_is_open(cx, live_id!(root), true, Animate::No); // Draw nodes recursively self.draw_node(cx, live_id!(root)); } DrawStep::done() } } ``` ### Registering Custom Draw Shaders For custom draw types with shader fields, use `script_shader`: ```rust script_mod!{ use mod.prelude.widgets_internal.* // Register custom draw shader set_type_default() do #(DrawMyShader::script_shader(vm)){ ..mod.draw.DrawQuad // Inherit from DrawQuad } // Register widget that uses it mod.widgets.MyWidgetBase = #(MyWidget::register_widget(vm)) } #[derive(Script, ScriptHook)] #[repr(C)] struct DrawMyShader { #[deref] draw_super: DrawQuad, #[live] my_param: f32, } ``` ### Registering Components (non-Widget) For structs that aren't full widgets but need script registration: ```rust script_mod!{ // For components (not widgets) mod.widgets.MyComponentBase = #(MyComponent::script_component(vm)) // For widgets (implements Widget trait) mod.widgets.MyWidgetBase = #(MyWidget::register_widget(vm)) } ``` ### Script Prelude Modules Two prelude modules available: - `mod.prelude.widgets_internal.*` - For internal widget library development - `mod.prelude.widgets.*` - For app development (includes all widgets) ```rust script_mod!{ // App development - use widgets prelude use mod.prelude.widgets.* // Or for widget library internals use mod.prelude.widgets_internal.* use mod.widgets.* } ``` ### Default Enum Values For enums with a `None` variant that need `Default`, use standard Rust `#[default]` attribute instead of `DefaultNone` derive: ```rust // Correct - use #[default] attribute on the None variant #[derive(Clone, Copy, Debug, PartialEq, Default)] pub enum MyAction { SomeAction, AnotherAction, #[default] None, } // Wrong - don't use DefaultNone derive #[derive(Clone, Copy, Debug, PartialEq, DefaultNone)] // Don't do this pub enum MyAction { SomeAction, None, } ``` ### Multi-Module Script Registration Pattern When refactoring a multi-file project (like studio) from `live_design!` to `script_mod!`: 1. **Each widget module** defines its own `script_mod!` that registers to `mod.widgets.*`: ```rust // In studio_editor.rs script_mod! { use mod.prelude.widgets_internal.* use mod.widgets.* mod.widgets.StudioCodeEditorBase = #(StudioCodeEditor::register_widget(vm)) mod.widgets.StudioCodeEditor = set_type_default() do mod.widgets.StudioCodeEditorBase { editor := CodeEditor {} } } ``` 2. **The lib.rs** aggregates all widget script_mods: ```rust pub fn script_mod(vm: &mut ScriptVm) { crate::module1::script_mod(vm); crate::module2::script_mod(vm); // ... all widget modules } ``` 3. **The app.rs** calls them in correct order: ```rust impl App { fn run(vm: &mut ScriptVm) -> Self { crate::makepad_widgets::script_mod(vm); // Base widgets first crate::script_mod(vm); // Your widget modules crate::app_ui::script_mod(vm); // UI that uses the widgets App::from_script_mod(vm, self::script_mod) } } ``` 4. **The app_ui.rs** can then use registered widgets: ```rust script_mod! { use mod.prelude.widgets.* // Now StudioCodeEditor is available from mod.widgets let EditorContent = View { editor := StudioCodeEditor {} } } ``` ### Cross-Module Sharing via `mod` Object **IMPORTANT**: `use crate.module.*` does NOT work in script_mod. The `crate.` prefix is not available. To share definitions between script_mod blocks in different files, store them in the `mod` object: ```rust // In app_ui.rs - export to mod.widgets namespace script_mod! { use mod.prelude.widgets.* // This makes AppUI available as mod.widgets.AppUI mod.widgets.AppUI = Window{ // ... } } // In app.rs - import via mod.widgets script_mod! { use mod.prelude.widgets.* use mod.widgets.* // Now AppUI is in scope load_all_resources() do #(App::script_component(vm)){ ui: Root{ AppUI{} } } } ``` The `mod` object is the only way to share data between script_mod blocks. ### Prelude Alias Syntax When defining a prelude, use `name:mod.path` to create an alias: ```rust mod.prelude.widgets = { ..mod.std, // Spread all of mod.std into scope theme:mod.theme, // Create 'theme' as alias for mod.theme draw:mod.draw, // Create 'draw' as alias for mod.draw } ``` Without the alias (just `mod.theme,`), the module is included but has no name - you can't access it! ### Let Bindings are Local `let` bindings in script_mod are LOCAL to that script_mod block. They cannot be: - Accessed from other script_mod blocks - Used as property values directly (e.g., `content +: MyLetBinding` won't work) To use a `let` binding, instantiate it: `MyLetBinding{}` or store it in `mod.*` for cross-module access. ### Debug Logging with `~` Use `~expression` to log the value of an expression during script evaluation: ```rust script_mod! { ~mod.theme // Logs the theme object ~mod.prelude.widgets // Logs what's in the prelude ~some_variable // Logs a variable's value (or "not found" error) } ``` ### Common Pitfalls **Widget ID references**: Named widget instances use `:=` in the DSL and plain names in Rust id macros: - DSL defines `code_block := View { ... }` → Rust uses `id!(code_block)` - DSL defines `my_button := Button { ... }` → Rust uses `ids!(my_button)` 1. **Missing `#[source]`**: All Script-derived structs need `#[source] source: ScriptObjectRef` 2. **Template scope**: Templates defined inside Dock aren't available outside; use `let` at script level 3. **Uniform vs Instance**: Use `instance()` for per-widget varying colors (like hover states on backgrounds) 4. **Forgot `+:`**: Without `+:`, you replace the entire property instead of merging 5. **Theme access**: Always `theme.color_x`, never `THEME_COLOR_X` or `(theme.color_x)` 6. **Missing widget registration**: Call `crate::makepad_widgets::script_mod(vm)` in `App::run()` before your own `script_mod`. Note: the old `live_design!` system and its crates are archived under `old/` 7. **Draw shader repr**: Custom draw shaders need `#[repr(C)]` for correct memory layout 8. **DefaultNone derive**: Don't use `DefaultNone` derive - use standard `#[derive(Default)]` with `#[default]` attribute on the `None` variant 9. **Script_mod call order**: Widget modules must be registered BEFORE UI modules that use them. Always call `lib.rs::script_mod` before `app_ui::script_mod` 10. **`pub` keyword invalid in script_mod**: Don't use `pub mod.widgets.X = ...`, just use `mod.widgets.X = ...`. Visibility is controlled by the Rust module system, not script_mod. 11. **Syntax for Inset/Align/Walk**: Use constructor syntax - `margin: Inset{left: 10}` not `margin: {left: 10}`, `align: Align{x: 0.5 y: 0.5}` not `align: {x: 0.5, y: 0.5}` 12. **Cursor values**: Use `cursor: MouseCursor.Hand` not `cursor: Hand` or `cursor: @Hand` 13. **Resource paths**: Use `crate_resource("self://path")` not `dep("crate://self/path")` 14. **Texture declarations in shaders**: Use `tex: texture_2d(float)` not `tex: texture2d` 15. **Enums not exposed to script**: Some Rust enums like `PopupMenuPosition::BelowInput` may not be exposed to script. If you get "not found" errors on enum variants, just remove the property and use the default 17. **Shader `mod` vs `modf`**: The Makepad shader language uses `modf(a, b)` for float modulo, NOT `mod(a, b)`. Similarly, use `atan2(y, x)` not `atan(y, x)` for two-argument arctangent. `atan(x)` (single arg) is also available. `fract(x)` works as expected. 16. **Draw shader struct field ordering**: In `#[repr(C)]` draw shader structs that extend another draw shader via `#[deref]`, NEVER place `#[rust]` or other non-instance data AFTER `DrawVars` and the instance fields. The system uses an unsafe pointer trick in `DrawVars::as_slice()` that reads contiguously past the end of `dyn_instances` into the subsequent `#[live]` fields. Any non-instance data between `DrawVars` and the instance fields will corrupt the GPU instance buffer. Put all extra data (like `#[rust]`, `#[live]` non-instance fields such as resource handles, booleans, etc.) BEFORE the `#[deref]` field, and only `#[live]` instance fields (the ones that map to shader inputs) AFTER. ```rust // CORRECT - non-instance data before deref, instance fields after #[derive(Script, ScriptHook)] #[repr(C)] pub struct MyDrawShader { #[live] pub svg: Option, // non-instance, BEFORE deref #[rust] my_state: bool, // non-instance, BEFORE deref #[deref] pub draw_super: DrawVector, // contains DrawVars + base instance fields #[live] pub tint: Vec4f, // instance field, AFTER deref - OK } // WRONG - rust data after instance fields breaks the memory layout #[derive(Script, ScriptHook)] #[repr(C)] pub struct MyDrawShader { #[deref] pub draw_super: DrawVector, #[live] pub tint: Vec4f, // instance field #[rust] my_state: bool, // BAD: sits between tint and the next shader's fields } ``` 18. **Don't put comments or blank lines before the first real code in `script!`/`script_mod!`**: Rust's proc macro token stream strips comments entirely — they produce no tokens. This shifts error column/line info because the span tracking starts from the first actual token. Always start with real code (e.g., `use mod.std.assert`) immediately after the opening brace. 19. **WARNING: Hex colors containing the letter `e` in `script_mod!`**: The Rust tokenizer interprets `e` or `E` in hex color literals as a scientific notation exponent, causing parse errors like `expected at least one digit in exponent`. For example, `#2ecc71` fails because `2e` looks like the start of `2e`. **Use the `#x` prefix** to escape this: write `#x2ecc71` instead of `#x2ecc71`. This applies to any hex color where a digit is immediately followed by `e`/`E` (e.g., `#1e1e2e`, `#4466ee`, `#7799ee`, `#bb99ee`). Colors without `e` (like `#ff4444`, `#44cc44`) work fine with plain `#`. 20. **Shader enums**: Prefer `match` on enum values with `_ =>` as the catch-all arm, not `if/else` chains over integer-like values. If enum `match` fails in shader compilation, treat it as a compiler bug: add or extend a `platform/script/test` case and fix the shader compiler path instead of rewriting shader logic to `if/else`.