Skip to content

Improve encapsulation of moxie_ui widgets/elems - #130

Merged
nixonyh merged 6 commits into
mainfrom
nixon/element-widget-split
Aug 5, 2026
Merged

Improve encapsulation of moxie_ui widgets/elems#130
nixonyh merged 6 commits into
mainfrom
nixon/element-widget-split

Conversation

@nixonyh

@nixonyh nixonyh commented Aug 4, 2026

Copy link
Copy Markdown
Member

The goal here is to encapsulate ui elements and widgets into their own modules so private consts, fns, structs stays private without leaking into unnecessary places.

This PR also deletes most of the marker components where it's become useless because of the use of moxie_ui_kernel bindings & watchers.

@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown

Review Change Stack

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 9b7e3afe-8dce-4092-b516-df8535d5354b

📥 Commits

Reviewing files that changed from the base of the PR and between 09a218d and 8740393.

📒 Files selected for processing (34)
  • editor/moxie/src/lib.rs
  • editor/moxie/src/playback.rs
  • editor/moxie/src/scene.rs
  • editor/moxie/src/ui.rs
  • editor/moxie/src/ui/hierarchy.rs
  • editor/moxie/src/ui/timeline.rs
  • editor/moxie/src/view.rs
  • editor/moxie_ui/examples/dock_demo.rs
  • editor/moxie_ui/src/elements.rs
  • editor/moxie_ui/src/elements/divider.rs
  • editor/moxie_ui/src/elements/frame.rs
  • editor/moxie_ui/src/elements/ghost_button.rs
  • editor/moxie_ui/src/elements/label.rs
  • editor/moxie_ui/src/elements/playhead.rs
  • editor/moxie_ui/src/elements/timeline_track.rs
  • editor/moxie_ui/src/glass/glass.wgsl
  • editor/moxie_ui/src/glass/material.rs
  • editor/moxie_ui/src/glass/mod.rs
  • editor/moxie_ui/src/icons.rs
  • editor/moxie_ui/src/lib.rs
  • editor/moxie_ui/src/reactive.rs
  • editor/moxie_ui/src/widgets.rs
  • editor/moxie_ui/src/widgets/dock.rs
  • editor/moxie_ui/src/widgets/dock/add_popup.rs
  • editor/moxie_ui/src/widgets/dock/area.rs
  • editor/moxie_ui/src/widgets/dock/drag.rs
  • editor/moxie_ui/src/widgets/dock/reconcile.rs
  • editor/moxie_ui/src/widgets/dock/registry.rs
  • editor/moxie_ui/src/widgets/dock/split.rs
  • editor/moxie_ui/src/widgets/dock/tabs.rs
  • editor/moxie_ui/src/widgets/dock/tree.rs
  • editor/moxie_ui/src/widgets/glass_backdrop.rs
  • editor/moxie_ui/src/widgets/inspector.rs
  • editor/moxie_ui/src/widgets/inspector/widget.rs
💤 Files with no reviewable changes (2)
  • editor/moxie_ui/src/glass/mod.rs
  • editor/moxie/src/scene.rs
🚧 Files skipped from review as they are similar to previous changes (28)
  • editor/moxie/src/playback.rs
  • editor/moxie_ui/src/widgets/glass_backdrop.rs
  • editor/moxie_ui/src/glass/material.rs
  • editor/moxie_ui/src/widgets/dock/reconcile.rs
  • editor/moxie_ui/src/elements/timeline_track.rs
  • editor/moxie_ui/src/widgets/dock.rs
  • editor/moxie_ui/src/elements/playhead.rs
  • editor/moxie/src/view.rs
  • editor/moxie_ui/src/icons.rs
  • editor/moxie_ui/src/widgets.rs
  • editor/moxie_ui/src/elements/divider.rs
  • editor/moxie_ui/src/glass/glass.wgsl
  • editor/moxie_ui/src/elements/frame.rs
  • editor/moxie_ui/src/widgets/dock/add_popup.rs
  • editor/moxie_ui/src/elements/ghost_button.rs
  • editor/moxie_ui/src/widgets/dock/tabs.rs
  • editor/moxie_ui/src/reactive.rs
  • editor/moxie_ui/examples/dock_demo.rs
  • editor/moxie/src/ui/hierarchy.rs
  • editor/moxie_ui/src/widgets/dock/split.rs
  • editor/moxie_ui/src/widgets/dock/drag.rs
  • editor/moxie_ui/src/elements/label.rs
  • editor/moxie/src/ui.rs
  • editor/moxie/src/lib.rs
  • editor/moxie_ui/src/widgets/dock/area.rs
  • editor/moxie_ui/src/widgets/inspector.rs
  • editor/moxie_ui/src/widgets/dock/tree.rs
  • editor/moxie/src/ui/timeline.rs

📝 Walkthrough

Summary by CodeRabbit

  • New Features
    • Introduced a redesigned editor interface with dockable viewport, hierarchy, timeline, and settings panels.
    • Added resizable panels, draggable tabs, tab reordering, panel splitting, and window management.
    • Added timeline playback controls, scrubbing, synchronized scrolling, playhead tracking, and dynamic tracks.
    • Added an editable settings inspector with support for boolean and numeric values.
    • Added reusable UI elements, including frames, labels, dividers, buttons, timeline tracks, and frosted-glass styling.

Walkthrough

The change reorganizes moxie_ui, adds dock and inspector systems, introduces reusable UI elements and glass rendering, and migrates the editor to UiPlugin with viewport, timeline, hierarchy, and settings windows.

Changes

Moxie UI and editor integration

Layer / File(s) Summary
UI foundation and reactive bootstrapping
editor/moxie_ui/src/{lib.rs,elements/*,glass/*,icons.rs,reactive.rs,widgets.rs}, editor/moxie_ui/src/widgets/glass_backdrop.rs
Adds reusable UI elements, shared icons, glass rendering, widget modules, plugin setup, and explicit reactive root construction.
Dock tree, registry, and split panels
editor/moxie_ui/src/widgets/dock/{tree.rs,registry.rs,split.rs}, editor/moxie_ui/src/widgets/dock.rs
Adds dock window registration, binary-tree layout state, tab operations, persistent leaves, split resizing, and tests.
Dock tabs, dragging, and popup state
editor/moxie_ui/src/widgets/dock/{add_popup.rs,area.rs,drag.rs,reconcile.rs,tabs.rs}
Adds component-scoped popup state, reusable dock buttons, structural icon queries, and drag-and-drop dock operations.
Reflection-based inspector
editor/moxie_ui/src/widgets/inspector*.rs
Adds reflected target and field access, editable leaf discovery, primitive inspectors, and synchronized boolean and numeric widgets.
Editor windows and timeline UI
editor/moxie/src/{lib.rs,playback.rs,ui.rs,view.rs}, editor/moxie/src/ui/{hierarchy.rs,timeline.rs}
Migrates the editor to UiPlugin and adds viewport, timeline, hierarchy, settings, playback, scrubbing, resizing, track, playhead, and settings-save behavior.
Dock demo migration
editor/moxie_ui/examples/dock_demo.rs
Updates the demo to use MoxieUiPlugin and explicit build_root construction.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Editor
  participant UiPlugin
  participant WindowRegistry
  participant DockTree
  participant TimelinePanel
  Editor->>UiPlugin: initialize editor UI
  UiPlugin->>WindowRegistry: register editor windows
  UiPlugin->>DockTree: build dock layout
  DockTree->>TimelinePanel: create timeline window
  TimelinePanel->>Editor: update playback and timeline state
Loading
sequenceDiagram
  participant User
  participant DockDragPlugin
  participant DockTree
  participant DockView
  User->>DockDragPlugin: drag dock tab
  DockDragPlugin->>DockView: display ghost and drop overlay
  DockDragPlugin->>DockTree: apply tab move or split
  DockTree->>DockView: reconcile dock components
Loading

Possibly related PRs

  • voxell-tech/motiongfx#116: The editor timeline UI and playback controls in this PR are reorganized from that earlier timeline work.
  • voxell-tech/motiongfx#117: This PR keeps the same timeline playback flow and scrubbing behavior.
  • voxell-tech/motiongfx#127: This PR uses the same reflection inspector architecture with InspectAppExt, ReflectInspect, InspectorTarget, and Field.

Suggested reviewers: jaghov

Poem

A rabbit hops through panes of light,
With tabs and tracks aligned just right.
The glass now gleams, the fields reply,
And dock trees shuffle by and by.
I twitch my nose at UI art—
New editor roots make every part.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: improved encapsulation of moxie_ui widgets and elements.
Description check ✅ Passed The description directly explains the module encapsulation and marker component removal included in the changeset.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@nixonyh
nixonyh requested review from Jaghov and Sheerwin02 August 4, 2026 15:47
Base automatically changed from nixon/moxie-icons to main August 5, 2026 06:47
@nixonyh
nixonyh force-pushed the nixon/element-widget-split branch from 7e64923 to 049fd98 Compare August 5, 2026 06:47

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

🧹 Nitpick comments (8)
editor/moxie_ui/src/lib.rs (1)

1-4: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

List icons in the crate doc.

Line 18 declares pub mod icons. The crate doc enumerates the public modules but omits icons. Add it so the enumeration matches the public surface.

📝 Proposed doc update
 //! Reusable `bevy_ui` widgets for the MotionGfx editor: [`elements`]
 //! (pure `bsn!` building blocks), [`widgets`] (kernel-composed trees:
 //! docking engine, reflect inspector), [`glass`] (frosted-glass
-//! material), [`reactive`] (kernel adapter), and [`theme`].
+//! material), [`reactive`] (kernel adapter), [`icons`] (shared icon
+//! asset paths), and [`theme`].
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@editor/moxie_ui/src/lib.rs` around lines 1 - 4, Update the crate-level
documentation module list to include the public icons module declared by pub mod
icons, alongside elements, widgets, glass, reactive, and theme.
editor/moxie_ui/src/elements/timeline_track.rs (1)

3-11: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Clarify that PIXELS_PER_SECOND belongs to the consuming app.

PIXELS_PER_SECOND is not defined in moxie_ui. The editor crate owns the pixels-per-second scale. This element only receives a resolved pixel width through TimelineTrackProps::width. State that ownership so the doc stays correct for other consumers of this crate.

📝 Proposed doc wording
 /// The scrubbable timeline track: a plain node sized to the track's
-/// duration (`PIXELS_PER_SECOND` per second), so a clip at time `t`
-/// sits at `t * PIXELS_PER_SECOND` from its left edge.
+/// duration. The consuming app resolves its own pixels-per-second
+/// scale and passes the result as `width`, so a clip at time `t`
+/// sits at `t * pixels_per_second` from the track's left edge.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@editor/moxie_ui/src/elements/timeline_track.rs` around lines 3 - 11, Update
the documentation for the timeline track to state that the consuming application
owns the PIXELS_PER_SECOND scale, while this element receives only the resolved
pixel width through TimelineTrackProps::width. Keep the existing description of
sizing and pointer-based scrubbing intact.
editor/moxie_ui/src/widgets/dock/drag.rs (3)

450-460: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the duplicated drag-teardown into one helper.

on_drag_end (lines 450-460) and cancel_drag_on_escape (lines 516-523) run the identical four-step teardown: clear the grab cursor, despawn the ghost, restore source_tab visibility, and despawn the overlay. Only the drop handling differs. Two copies of teardown will drift as the drag state grows fields.

♻️ Suggested helper
/// Tear down the transient drag entities and the grab cursor.
fn teardown_drag(
    commands: &mut Commands,
    override_cursor: &mut OverrideCursor,
    source_tab: Entity,
    ghost_entity: Entity,
    overlay_entity: Option<Entity>,
) {
    clear_grab_cursor(override_cursor);
    commands.entity(ghost_entity).despawn();
    // A consumed drop rebuilds the leaf and despawns the tab anyway,
    // so restoring visibility is best-effort.
    commands.entity(source_tab).try_insert(Visibility::Inherited);
    if let Some(overlay) = overlay_entity {
        commands.entity(overlay).despawn();
    }
}

Also applies to: 516-523

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@editor/moxie_ui/src/widgets/dock/drag.rs` around lines 450 - 460, Extract the
shared drag cleanup from on_drag_end and cancel_drag_on_escape into a
teardown_drag helper that accepts Commands, OverrideCursor, source_tab,
ghost_entity, and optional overlay_entity. Move the four cleanup
operations—clear the cursor, despawn the ghost, restore source_tab visibility,
and despawn the overlay—into the helper, then call it from both handlers while
leaving their distinct drop or cancellation logic unchanged.

700-733: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Rename the nested is_far_side so it does not shadow the outer function.

The outer is_far_side defines a nested fn is_far_side with the same name and calls it four times. Rust resolves those calls to the nested item because the item shadows the outer name inside the body. The code is correct, but a reader must resolve the shadowing before the four call sites make sense, and the return match { .. }; followed by an item definition compounds it.

♻️ Proposed rename
 fn is_far_side(
     mouse_pos: Vec2,
     child_pos: Vec2,
     parent: &Node,
 ) -> (bool, bool) {
     return match parent.flex_direction {
         FlexDirection::Row => {
-            (is_far_side(mouse_pos, child_pos, false), false)
+            (past_center(mouse_pos, child_pos, false), false)
         }
         FlexDirection::RowReverse => {
-            (!is_far_side(mouse_pos, child_pos, false), false)
+            (!past_center(mouse_pos, child_pos, false), false)
         }
         FlexDirection::Column => {
-            (is_far_side(mouse_pos, child_pos, true), true)
+            (past_center(mouse_pos, child_pos, true), true)
         }
         FlexDirection::ColumnReverse => {
-            (!is_far_side(mouse_pos, child_pos, true), true)
+            (!past_center(mouse_pos, child_pos, true), true)
         }
     };
 
-    fn is_far_side(
+    fn past_center(
         mouse_pos: Vec2,
         child_pos: Vec2,
         is_vertical: bool,
     ) -> bool {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@editor/moxie_ui/src/widgets/dock/drag.rs` around lines 700 - 733, Rename the
nested helper function inside the outer is_far_side to a distinct descriptive
name, and update all four FlexDirection match-arm call sites to use it. Leave
the outer is_far_side signature and its direction-handling behavior unchanged.

210-217: 🚀 Performance & Scalability | 🔵 Trivial | ⚖️ Poor tradeoff

Reuse the drop overlay entity instead of respawning it per drag event.

on_drag_move despawns overlay_entity and spawns a replacement on every Pointer<Drag> event. During a drag this creates and destroys an entity each frame, which churns archetypes and command buffers for a node whose only changing fields are left, top, width, and height.

Keep one overlay entity for the lifetime of the drag and update its Node and Visibility, the same way ghost_node already updates the ghost in place at line 210.

Also applies to: 316-336

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@editor/moxie_ui/src/widgets/dock/drag.rs` around lines 210 - 217, Update
on_drag_move to reuse the existing overlay entity throughout the drag instead of
despawning old_overlay and creating a replacement on each event. When the drop
target changes, update the overlay’s Node fields (left, top, width, and height)
and Visibility in place, while preserving the existing overlay entity and
ghost_node update flow.
editor/moxie_ui/src/widgets/dock/add_popup.rs (1)

26-32: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Make the single-overlay assumption fail loudly.

AddWindowPopupState is now a component, and three handlers reach it with q_state.single_mut(). single_mut returns Err for zero matches and for two or more matches. If a second dock root is ever mounted, every "+" button and every outside-click stops closing the popup, with no log line and no panic. The failure is silent.

Add a debug assertion or a warning at the query sites, or scope the lookup to the owning overlay entity the same way build_popup already does with ui.parent().

♻️ Suggested guard in `on_add_click`
     let Ok(mut state) = q_state.single_mut() else {
+        debug_assert!(
+            false,
+            "expected exactly one AddWindowPopupState overlay"
+        );
         return;
     };

Also applies to: 58-60

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@editor/moxie_ui/src/widgets/dock/add_popup.rs` around lines 26 - 32, Make the
single-overlay invariant explicit at every handler querying AddWindowPopupState,
including on_add_click and the handlers around the referenced lines: handle
q_state.single_mut() errors with a debug assertion or warning so zero or
multiple matches are observable, or scope each query to the owning overlay
entity consistently with build_popup and ui.parent().
editor/moxie_ui/src/widgets/dock/tree.rs (1)

770-1012: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add coverage for the remaining public tree operations.

The suite covers split, move, reorder, and simplify well. Three public methods stay untested: remove_window_kind, insert_tab with an explicit index into a different leaf, and iter_dfs. remove_window_kind calls simplify once per removed tab, so its interaction with collapsing leaves is the highest-value gap.

🧪 Suggested test for `remove_window_kind`
#[test]
fn remove_window_kind_drops_every_instance() {
    let mut t = DockTree::new();
    let root = t.set_root_leaf(leaf("root", &["a", "outliner"]));
    let (right, _) = t.split(root, Edge::Right, "outliner".into()).unwrap();
    let _ = right;

    t.remove_window_kind("outliner");

    // Only "a" survives, and the drained split collapsed.
    assert!(matches!(t.nodes[&t.root.unwrap()], DockNode::Leaf(_)));
    assert_eq!(window_ids(&t, t.root.unwrap()), vec!["a"]);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@editor/moxie_ui/src/widgets/dock/tree.rs` around lines 770 - 1012, Add tests
covering the three untested public operations: verify remove_window_kind removes
every matching tab and simplifies drained leaves/splits, verify insert_tab with
an explicit index inserts into a different destination leaf at the requested
position and activates the tab, and verify iter_dfs visits the tree nodes in
depth-first order. Add these tests alongside the existing DockTree tests,
reusing helpers such as window_ids and tab_id_for.
editor/moxie/src/ui.rs (1)

164-224: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the window id literals into constants.

The ids "viewport", "timeline", "hierarchy" and "settings" appear both here and in setup_editor_ui at Lines 116-118, 121, 124 and 127. WindowRegistry resolves the dock leaf's window ids against the registered descriptors at runtime, so a typo in either place produces an empty tab with no compile-time error.

Define the ids once and use them in both functions.

♻️ Proposed refactor
 pub(crate) const PANEL_PADDING: f32 = 12.0;
+
+const WIN_VIEWPORT: &str = "viewport";
+const WIN_TIMELINE: &str = "timeline";
+const WIN_HIERARCHY: &str = "hierarchy";
+const WIN_SETTINGS: &str = "settings";

Then use them at both sites, for example:

     let viewport = tree.set_root_leaf(
-        DockLeaf::new("viewport", DockAreaStyle::TabBar)
+        DockLeaf::new(WIN_VIEWPORT, DockAreaStyle::TabBar)
             .with_windows(vec![
-                "viewport".into(),
-                "hierarchy".into(),
-                "settings".into(),
+                WIN_VIEWPORT.into(),
+                WIN_HIERARCHY.into(),
+                WIN_SETTINGS.into(),
             ]),
     );
-    tree.split(viewport, Edge::Bottom, "timeline".into());
+    tree.split(viewport, Edge::Bottom, WIN_TIMELINE.into());
     registry.register(DockWindowDescriptor {
-        id: "timeline".into(),
+        id: WIN_TIMELINE.into(),
         name: "Timeline".into(),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@editor/moxie/src/ui.rs` around lines 164 - 224, Define shared constants for
the window IDs "viewport", "timeline", "hierarchy", and "settings" in the
appropriate UI module scope, then replace the literals in both the dock
descriptor registrations and setup_editor_ui with those constants. Ensure
WindowRegistry registration and dock leaf references use the same symbols.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@editor/moxie_ui/src/glass/glass.wgsl`:
- Line 46: Update the shape calculation around the WGSL `shape` expression to
use a hard mask when `extra.x` is zero, while retaining the existing smooth
feathering behavior for nonzero values. Ensure the zero-feather path does not
force a one-pixel feather through `max(extra.x, 1.0)`.

In `@editor/moxie_ui/src/lib.rs`:
- Around line 33-50: Update MoxieUiPlugin’s documentation to remove the claim
that DockPlugin pulls in GlassPlugin, since GlassPlugin is registered explicitly
in the plugin tuple. Keep the existing GlassPlugin registration unchanged.

In `@editor/moxie_ui/src/reactive.rs`:
- Around line 250-261: Update component_changed so an absent C returns false on
the initial poll and remains treated as unchanged until a component value is
present, rather than delegating directly to value_changed with Option<C>.
Preserve change detection for inserted or modified C values and use the existing
world.get::<C> lookup.

In `@editor/moxie_ui/src/widgets/dock/drag.rs`:
- Around line 88-113: Update on_tab_drag_start to return immediately when
drag_state is already in an active drag state, especially
DockDragState::Dragging, before resolving the tab or assigning PendingDrag.
Preserve the existing initialization for idle or pending states as appropriate,
ensuring an in-flight drag’s ghost_entity, overlay_entity, and hidden source tab
remain intact.

In `@editor/moxie_ui/src/widgets/dock/split.rs`:
- Around line 207-230: Update on_handle_drag_end to clear override_cursor before
looking up the parent Node, so cleanup still occurs when the parent is missing
or reconfigured. Remove the dependency on the current flex_direction and clear
the override whenever it contains either supported resize cursor icon, while
preserving the existing drag-marker cleanup.

In `@editor/moxie_ui/src/widgets/inspector.rs`:
- Around line 361-379: Update the label construction in the inspector row around
Field::new and collect_leaves so it uses the final reflect-path segment, rather
than the full raw path. When the path is empty, fall back to the target’s
reflected type name; preserve readable nested labels instead of rendering bare
or empty text.

In `@editor/moxie_ui/src/widgets/inspector/widget.rs`:
- Around line 197-200: Update the u64 conversion entry so its outbound to_input
closure saturates values above i64::MAX instead of casting and wrapping;
preserve the existing inbound nonnegative clamp, ensuring both directions
saturate and u64::MAX displays as i64::MAX without losing the target value on
round trip.
- Around line 87-118: Remove the immediate Checked insert/remove operations from
the ValueChange observer and let bind_raw synchronize the checkbox after a
successful field.set, keeping the observer focused on queuing the write. Ensure
the queued write targets the Checkbox root node by using node rather than
change.source where the entity is required, and update the misleading
controlled-state comment accordingly.

In `@editor/moxie/src/ui.rs`:
- Around line 79-106: Document in the reflected EditorSettings definitions that
hdr and physical_size are restart-only settings because setup_editor_ui reads
them only during startup. Ensure the Settings UI communicates that runtime
changes are not applied, without altering setup_editor_ui or adding live-update
handling.

In `@editor/moxie/src/ui/timeline.rs`:
- Around line 262-281: Update on_divider_drag to base the panel width on the
pointer’s absolute drag distance rather than accumulating drag.delta.x. Add the
corresponding DragStart observer/state alongside the existing observer to record
the initial width, then apply drag.distance.x to that starting width before
clamping to NAME_PANEL_MIN and NAME_PANEL_MAX, matching handle_panel_drag’s
approach.

---

Nitpick comments:
In `@editor/moxie_ui/src/elements/timeline_track.rs`:
- Around line 3-11: Update the documentation for the timeline track to state
that the consuming application owns the PIXELS_PER_SECOND scale, while this
element receives only the resolved pixel width through
TimelineTrackProps::width. Keep the existing description of sizing and
pointer-based scrubbing intact.

In `@editor/moxie_ui/src/lib.rs`:
- Around line 1-4: Update the crate-level documentation module list to include
the public icons module declared by pub mod icons, alongside elements, widgets,
glass, reactive, and theme.

In `@editor/moxie_ui/src/widgets/dock/add_popup.rs`:
- Around line 26-32: Make the single-overlay invariant explicit at every handler
querying AddWindowPopupState, including on_add_click and the handlers around the
referenced lines: handle q_state.single_mut() errors with a debug assertion or
warning so zero or multiple matches are observable, or scope each query to the
owning overlay entity consistently with build_popup and ui.parent().

In `@editor/moxie_ui/src/widgets/dock/drag.rs`:
- Around line 450-460: Extract the shared drag cleanup from on_drag_end and
cancel_drag_on_escape into a teardown_drag helper that accepts Commands,
OverrideCursor, source_tab, ghost_entity, and optional overlay_entity. Move the
four cleanup operations—clear the cursor, despawn the ghost, restore source_tab
visibility, and despawn the overlay—into the helper, then call it from both
handlers while leaving their distinct drop or cancellation logic unchanged.
- Around line 700-733: Rename the nested helper function inside the outer
is_far_side to a distinct descriptive name, and update all four FlexDirection
match-arm call sites to use it. Leave the outer is_far_side signature and its
direction-handling behavior unchanged.
- Around line 210-217: Update on_drag_move to reuse the existing overlay entity
throughout the drag instead of despawning old_overlay and creating a replacement
on each event. When the drop target changes, update the overlay’s Node fields
(left, top, width, and height) and Visibility in place, while preserving the
existing overlay entity and ghost_node update flow.

In `@editor/moxie_ui/src/widgets/dock/tree.rs`:
- Around line 770-1012: Add tests covering the three untested public operations:
verify remove_window_kind removes every matching tab and simplifies drained
leaves/splits, verify insert_tab with an explicit index inserts into a different
destination leaf at the requested position and activates the tab, and verify
iter_dfs visits the tree nodes in depth-first order. Add these tests alongside
the existing DockTree tests, reusing helpers such as window_ids and tab_id_for.

In `@editor/moxie/src/ui.rs`:
- Around line 164-224: Define shared constants for the window IDs "viewport",
"timeline", "hierarchy", and "settings" in the appropriate UI module scope, then
replace the literals in both the dock descriptor registrations and
setup_editor_ui with those constants. Ensure WindowRegistry registration and
dock leaf references use the same symbols.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 3b3883e4-6963-4dd2-a940-ad417392fa64

📥 Commits

Reviewing files that changed from the base of the PR and between 09a218d and 049fd98.

📒 Files selected for processing (34)
  • editor/moxie/src/lib.rs
  • editor/moxie/src/playback.rs
  • editor/moxie/src/scene.rs
  • editor/moxie/src/ui.rs
  • editor/moxie/src/ui/hierarchy.rs
  • editor/moxie/src/ui/timeline.rs
  • editor/moxie/src/view.rs
  • editor/moxie_ui/examples/dock_demo.rs
  • editor/moxie_ui/src/elements.rs
  • editor/moxie_ui/src/elements/divider.rs
  • editor/moxie_ui/src/elements/frame.rs
  • editor/moxie_ui/src/elements/ghost_button.rs
  • editor/moxie_ui/src/elements/label.rs
  • editor/moxie_ui/src/elements/playhead.rs
  • editor/moxie_ui/src/elements/timeline_track.rs
  • editor/moxie_ui/src/glass/glass.wgsl
  • editor/moxie_ui/src/glass/material.rs
  • editor/moxie_ui/src/glass/mod.rs
  • editor/moxie_ui/src/icons.rs
  • editor/moxie_ui/src/lib.rs
  • editor/moxie_ui/src/reactive.rs
  • editor/moxie_ui/src/widgets.rs
  • editor/moxie_ui/src/widgets/dock.rs
  • editor/moxie_ui/src/widgets/dock/add_popup.rs
  • editor/moxie_ui/src/widgets/dock/area.rs
  • editor/moxie_ui/src/widgets/dock/drag.rs
  • editor/moxie_ui/src/widgets/dock/reconcile.rs
  • editor/moxie_ui/src/widgets/dock/registry.rs
  • editor/moxie_ui/src/widgets/dock/split.rs
  • editor/moxie_ui/src/widgets/dock/tabs.rs
  • editor/moxie_ui/src/widgets/dock/tree.rs
  • editor/moxie_ui/src/widgets/glass_backdrop.rs
  • editor/moxie_ui/src/widgets/inspector.rs
  • editor/moxie_ui/src/widgets/inspector/widget.rs
💤 Files with no reviewable changes (2)
  • editor/moxie_ui/src/glass/mod.rs
  • editor/moxie/src/scene.rs

Comment thread editor/moxie_ui/src/lib.rs
Comment on lines +250 to +261
/// Fires when the watched node's `C` differs from the last poll.
///
/// The entity-local counterpart to [`resource_changed`]: state for a
/// single widget instance (a popup's open/closed, a field's edit
/// buffer) belongs on that widget's own node, not in a global
/// `Resource` that every instance of the widget would have to share.
/// `C` absent reads as unchanged, not a rebuild — a node that hasn't
/// had its state inserted yet is not yet ready to build.
pub fn component_changed<C: Component + Clone + PartialEq>()
-> impl FnMut(&World, Entity) -> bool {
value_changed(|world, node| world.get::<C>(node).cloned())
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

component_changed fires on the first poll when C is absent.

The doc at lines 256-257 states that an absent C reads as unchanged. The implementation does not hold that promise.

value_changed is instantiated with T = Option<C> and starts with seen: Option<Option<C>> = None. On the first poll with C absent, current is None, so the comparison is None != Some(&None), which is true. The watcher fires and the widget builds once before its state component exists. The intended behavior is to wait until the state lands.

Track the absent case explicitly instead of delegating to value_changed.

🐛 Proposed fix
 pub fn component_changed<C: Component + Clone + PartialEq>()
 -> impl FnMut(&World, Entity) -> bool {
-    value_changed(|world, node| world.get::<C>(node).cloned())
+    let mut seen: Option<C> = None;
+    move |world, node| {
+        // `C` absent is not a rebuild: the node is not ready yet.
+        let Some(current) = world.get::<C>(node) else {
+            return false;
+        };
+        let fired = seen.as_ref() != Some(current);
+        seen = Some(current.clone());
+        fired
+    }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/// Fires when the watched node's `C` differs from the last poll.
///
/// The entity-local counterpart to [`resource_changed`]: state for a
/// single widget instance (a popup's open/closed, a field's edit
/// buffer) belongs on that widget's own node, not in a global
/// `Resource` that every instance of the widget would have to share.
/// `C` absent reads as unchanged, not a rebuild — a node that hasn't
/// had its state inserted yet is not yet ready to build.
pub fn component_changed<C: Component + Clone + PartialEq>()
-> impl FnMut(&World, Entity) -> bool {
value_changed(|world, node| world.get::<C>(node).cloned())
}
/// Fires when the watched node's `C` differs from the last poll.
///
/// The entity-local counterpart to [`resource_changed`]: state for a
/// single widget instance (a popup's open/closed, a field's edit
/// buffer) belongs on that widget's own node, not in a global
/// `Resource` that every instance of the widget would have to share.
/// `C` absent reads as unchanged, not a rebuild — a node that hasn't
/// had its state inserted yet is not yet ready to build.
pub fn component_changed<C: Component + Clone + PartialEq>()
-> impl FnMut(&World, Entity) -> bool {
let mut seen: Option<C> = None;
move |world, node| {
// `C` absent is not a rebuild: the node is not ready yet.
let Some(current) = world.get::<C>(node) else {
return false;
};
let fired = seen.as_ref() != Some(current);
seen = Some(current.clone());
fired
}
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@editor/moxie_ui/src/reactive.rs` around lines 250 - 261, Update
component_changed so an absent C returns false on the initial poll and remains
treated as unchanged until a component value is present, rather than delegating
directly to value_changed with Option<C>. Preserve change detection for inserted
or modified C values and use the existing world.get::<C> lookup.

Comment thread editor/moxie/src/ui.rs
Comment on lines +79 to +106
let size = settings.physical_size.max(UVec2::ONE);
let preview = images.add(Image::new_target_texture(
size.x,
size.y,
TextureFormat::Rgba8Unorm,
Some(TextureFormat::Rgba8UnormSrgb),
));
commands.insert_resource(PreviewImage(preview.clone()));

// Own render layer so this camera doesn't also pick up scene
// meshes (e.g. bevy_vello's composite quad, layer 0)
// full-window. `IsDefaultUiCamera` catches dock UI spawned
// without a target (drag ghosts, drop overlays).
let ui_camera = commands
.spawn_scene(bsn! [
Camera2d
Camera {
order: 10,
clear_color: Color::BLACK,
}
TrackViewportCamera
])
.insert((RenderLayers::layer(1), IsDefaultUiCamera))
.id();

if settings.hdr {
commands.entity(ui_camera).insert(Hdr);
}

@coderabbitai coderabbitai Bot Aug 5, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check whether any system reacts to EditorSettings changes to re-apply hdr / physical_size.
set -euo pipefail

rg -n -C 5 'struct EditorSettings' editor --type=rust
rg -n -C 4 'EditorSettings' editor --type=rust -g '!**/ui.rs'
rg -n -C 4 '\bHdr\b|physical_size' editor --type=rust

Repository: voxell-tech/motiongfx

Length of output: 7002


Document hdr and physical_size as restart-only settings.

setup_editor_ui runs only once in Startup and reads these EditorSettings fields. The Settings window exposes all reflected EditorSettings fields, so runtime changes to hdr or physical_size are not re-applied. If live updates are not intended, document that; otherwise, add an update handler to re-apply them.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@editor/moxie/src/ui.rs` around lines 79 - 106, Document in the reflected
EditorSettings definitions that hdr and physical_size are restart-only settings
because setup_editor_ui reads them only during startup. Ensure the Settings UI
communicates that runtime changes are not applied, without altering
setup_editor_ui or adding live-update handling.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Known issue, please create one to track this, actually all the other settings aren't updated too.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

repo="voxell-tech/motiongfx"
body=$(cat <<'EOF'
## Summary

Runtime changes to `EditorSettings` are not applied to the running editor.

`setup_editor_ui` reads settings during `Startup`. The Settings window exposes reflected `EditorSettings` fields, but the editor does not react when those fields change. This includes `hdr`, `physical_size`, and the other exposed settings.

## Required changes

Define the runtime-update behavior for each exposed `EditorSettings` field.

For settings that support live updates, add change handling that updates the affected editor UI, camera, render target, or related state.

For settings that require restart, communicate that requirement in the Settings UI and documentation.

## Affected areas

- `editor/moxie/src/ui.rs`
- `EditorSettings` definition and reflection registration
- Settings window and inspector integration
- Systems that consume editor settings

## Rationale

The Settings window currently permits edits that do not take effect. This creates an incorrect runtime expectation.

## Acceptance criteria

- Each exposed `EditorSettings` field has defined behavior after a runtime edit.
- Live-update settings are applied without restarting the editor.
- Restart-only settings are clearly identified in the Settings UI.
- Tests or targeted validation cover the selected behavior.

## Backlinks

- Pull request: https://github.com/voxell-tech/motiongfx/pull/130
- Review comment: https://github.com/voxell-tech/motiongfx/pull/130#discussion_r3718573287
- Requested by: `@nixonyh`
EOF
)

gh issue create \
  --repo "$repo" \
  --title "Apply EditorSettings changes at runtime or mark them restart-only" \
  --body "$body" \
  --assignee "nixonyh"

Length of output: 1918


@nixonyh, created and assigned follow-up issue #133.

It tracks runtime application or restart-only labeling for all exposed EditorSettings fields, not only hdr and physical_size.


✏️ Learnings added
Learnt from: nixonyh
Repo: voxell-tech/motiongfx PR: 130
File: editor/moxie/src/ui.rs:79-106
Timestamp: 2026-08-05T09:45:41.099Z
Learning: In the `editor/moxie` editor, the Settings window exposes `EditorSettings` fields, but runtime edits to all exposed fields are currently not applied. Follow-up issue `#133` tracks defining live-update behavior or clear restart-only labeling for each setting.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

You are interacting with an AI system.

Comment thread editor/moxie/src/ui/timeline.rs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 10

🧹 Nitpick comments (8)
editor/moxie_ui/src/lib.rs (1)

1-4: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

List icons in the crate doc.

Line 18 declares pub mod icons. The crate doc enumerates the public modules but omits icons. Add it so the enumeration matches the public surface.

📝 Proposed doc update
 //! Reusable `bevy_ui` widgets for the MotionGfx editor: [`elements`]
 //! (pure `bsn!` building blocks), [`widgets`] (kernel-composed trees:
 //! docking engine, reflect inspector), [`glass`] (frosted-glass
-//! material), [`reactive`] (kernel adapter), and [`theme`].
+//! material), [`reactive`] (kernel adapter), [`icons`] (shared icon
+//! asset paths), and [`theme`].
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@editor/moxie_ui/src/lib.rs` around lines 1 - 4, Update the crate-level
documentation module list to include the public icons module declared by pub mod
icons, alongside elements, widgets, glass, reactive, and theme.
editor/moxie_ui/src/elements/timeline_track.rs (1)

3-11: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Clarify that PIXELS_PER_SECOND belongs to the consuming app.

PIXELS_PER_SECOND is not defined in moxie_ui. The editor crate owns the pixels-per-second scale. This element only receives a resolved pixel width through TimelineTrackProps::width. State that ownership so the doc stays correct for other consumers of this crate.

📝 Proposed doc wording
 /// The scrubbable timeline track: a plain node sized to the track's
-/// duration (`PIXELS_PER_SECOND` per second), so a clip at time `t`
-/// sits at `t * PIXELS_PER_SECOND` from its left edge.
+/// duration. The consuming app resolves its own pixels-per-second
+/// scale and passes the result as `width`, so a clip at time `t`
+/// sits at `t * pixels_per_second` from the track's left edge.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@editor/moxie_ui/src/elements/timeline_track.rs` around lines 3 - 11, Update
the documentation for the timeline track to state that the consuming application
owns the PIXELS_PER_SECOND scale, while this element receives only the resolved
pixel width through TimelineTrackProps::width. Keep the existing description of
sizing and pointer-based scrubbing intact.
editor/moxie_ui/src/widgets/dock/drag.rs (3)

450-460: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the duplicated drag-teardown into one helper.

on_drag_end (lines 450-460) and cancel_drag_on_escape (lines 516-523) run the identical four-step teardown: clear the grab cursor, despawn the ghost, restore source_tab visibility, and despawn the overlay. Only the drop handling differs. Two copies of teardown will drift as the drag state grows fields.

♻️ Suggested helper
/// Tear down the transient drag entities and the grab cursor.
fn teardown_drag(
    commands: &mut Commands,
    override_cursor: &mut OverrideCursor,
    source_tab: Entity,
    ghost_entity: Entity,
    overlay_entity: Option<Entity>,
) {
    clear_grab_cursor(override_cursor);
    commands.entity(ghost_entity).despawn();
    // A consumed drop rebuilds the leaf and despawns the tab anyway,
    // so restoring visibility is best-effort.
    commands.entity(source_tab).try_insert(Visibility::Inherited);
    if let Some(overlay) = overlay_entity {
        commands.entity(overlay).despawn();
    }
}

Also applies to: 516-523

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@editor/moxie_ui/src/widgets/dock/drag.rs` around lines 450 - 460, Extract the
shared drag cleanup from on_drag_end and cancel_drag_on_escape into a
teardown_drag helper that accepts Commands, OverrideCursor, source_tab,
ghost_entity, and optional overlay_entity. Move the four cleanup
operations—clear the cursor, despawn the ghost, restore source_tab visibility,
and despawn the overlay—into the helper, then call it from both handlers while
leaving their distinct drop or cancellation logic unchanged.

700-733: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Rename the nested is_far_side so it does not shadow the outer function.

The outer is_far_side defines a nested fn is_far_side with the same name and calls it four times. Rust resolves those calls to the nested item because the item shadows the outer name inside the body. The code is correct, but a reader must resolve the shadowing before the four call sites make sense, and the return match { .. }; followed by an item definition compounds it.

♻️ Proposed rename
 fn is_far_side(
     mouse_pos: Vec2,
     child_pos: Vec2,
     parent: &Node,
 ) -> (bool, bool) {
     return match parent.flex_direction {
         FlexDirection::Row => {
-            (is_far_side(mouse_pos, child_pos, false), false)
+            (past_center(mouse_pos, child_pos, false), false)
         }
         FlexDirection::RowReverse => {
-            (!is_far_side(mouse_pos, child_pos, false), false)
+            (!past_center(mouse_pos, child_pos, false), false)
         }
         FlexDirection::Column => {
-            (is_far_side(mouse_pos, child_pos, true), true)
+            (past_center(mouse_pos, child_pos, true), true)
         }
         FlexDirection::ColumnReverse => {
-            (!is_far_side(mouse_pos, child_pos, true), true)
+            (!past_center(mouse_pos, child_pos, true), true)
         }
     };
 
-    fn is_far_side(
+    fn past_center(
         mouse_pos: Vec2,
         child_pos: Vec2,
         is_vertical: bool,
     ) -> bool {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@editor/moxie_ui/src/widgets/dock/drag.rs` around lines 700 - 733, Rename the
nested helper function inside the outer is_far_side to a distinct descriptive
name, and update all four FlexDirection match-arm call sites to use it. Leave
the outer is_far_side signature and its direction-handling behavior unchanged.

210-217: 🚀 Performance & Scalability | 🔵 Trivial | ⚖️ Poor tradeoff

Reuse the drop overlay entity instead of respawning it per drag event.

on_drag_move despawns overlay_entity and spawns a replacement on every Pointer<Drag> event. During a drag this creates and destroys an entity each frame, which churns archetypes and command buffers for a node whose only changing fields are left, top, width, and height.

Keep one overlay entity for the lifetime of the drag and update its Node and Visibility, the same way ghost_node already updates the ghost in place at line 210.

Also applies to: 316-336

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@editor/moxie_ui/src/widgets/dock/drag.rs` around lines 210 - 217, Update
on_drag_move to reuse the existing overlay entity throughout the drag instead of
despawning old_overlay and creating a replacement on each event. When the drop
target changes, update the overlay’s Node fields (left, top, width, and height)
and Visibility in place, while preserving the existing overlay entity and
ghost_node update flow.
editor/moxie_ui/src/widgets/dock/add_popup.rs (1)

26-32: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Make the single-overlay assumption fail loudly.

AddWindowPopupState is now a component, and three handlers reach it with q_state.single_mut(). single_mut returns Err for zero matches and for two or more matches. If a second dock root is ever mounted, every "+" button and every outside-click stops closing the popup, with no log line and no panic. The failure is silent.

Add a debug assertion or a warning at the query sites, or scope the lookup to the owning overlay entity the same way build_popup already does with ui.parent().

♻️ Suggested guard in `on_add_click`
     let Ok(mut state) = q_state.single_mut() else {
+        debug_assert!(
+            false,
+            "expected exactly one AddWindowPopupState overlay"
+        );
         return;
     };

Also applies to: 58-60

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@editor/moxie_ui/src/widgets/dock/add_popup.rs` around lines 26 - 32, Make the
single-overlay invariant explicit at every handler querying AddWindowPopupState,
including on_add_click and the handlers around the referenced lines: handle
q_state.single_mut() errors with a debug assertion or warning so zero or
multiple matches are observable, or scope each query to the owning overlay
entity consistently with build_popup and ui.parent().
editor/moxie_ui/src/widgets/dock/tree.rs (1)

770-1012: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add coverage for the remaining public tree operations.

The suite covers split, move, reorder, and simplify well. Three public methods stay untested: remove_window_kind, insert_tab with an explicit index into a different leaf, and iter_dfs. remove_window_kind calls simplify once per removed tab, so its interaction with collapsing leaves is the highest-value gap.

🧪 Suggested test for `remove_window_kind`
#[test]
fn remove_window_kind_drops_every_instance() {
    let mut t = DockTree::new();
    let root = t.set_root_leaf(leaf("root", &["a", "outliner"]));
    let (right, _) = t.split(root, Edge::Right, "outliner".into()).unwrap();
    let _ = right;

    t.remove_window_kind("outliner");

    // Only "a" survives, and the drained split collapsed.
    assert!(matches!(t.nodes[&t.root.unwrap()], DockNode::Leaf(_)));
    assert_eq!(window_ids(&t, t.root.unwrap()), vec!["a"]);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@editor/moxie_ui/src/widgets/dock/tree.rs` around lines 770 - 1012, Add tests
covering the three untested public operations: verify remove_window_kind removes
every matching tab and simplifies drained leaves/splits, verify insert_tab with
an explicit index inserts into a different destination leaf at the requested
position and activates the tab, and verify iter_dfs visits the tree nodes in
depth-first order. Add these tests alongside the existing DockTree tests,
reusing helpers such as window_ids and tab_id_for.
editor/moxie/src/ui.rs (1)

164-224: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the window id literals into constants.

The ids "viewport", "timeline", "hierarchy" and "settings" appear both here and in setup_editor_ui at Lines 116-118, 121, 124 and 127. WindowRegistry resolves the dock leaf's window ids against the registered descriptors at runtime, so a typo in either place produces an empty tab with no compile-time error.

Define the ids once and use them in both functions.

♻️ Proposed refactor
 pub(crate) const PANEL_PADDING: f32 = 12.0;
+
+const WIN_VIEWPORT: &str = "viewport";
+const WIN_TIMELINE: &str = "timeline";
+const WIN_HIERARCHY: &str = "hierarchy";
+const WIN_SETTINGS: &str = "settings";

Then use them at both sites, for example:

     let viewport = tree.set_root_leaf(
-        DockLeaf::new("viewport", DockAreaStyle::TabBar)
+        DockLeaf::new(WIN_VIEWPORT, DockAreaStyle::TabBar)
             .with_windows(vec![
-                "viewport".into(),
-                "hierarchy".into(),
-                "settings".into(),
+                WIN_VIEWPORT.into(),
+                WIN_HIERARCHY.into(),
+                WIN_SETTINGS.into(),
             ]),
     );
-    tree.split(viewport, Edge::Bottom, "timeline".into());
+    tree.split(viewport, Edge::Bottom, WIN_TIMELINE.into());
     registry.register(DockWindowDescriptor {
-        id: "timeline".into(),
+        id: WIN_TIMELINE.into(),
         name: "Timeline".into(),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@editor/moxie/src/ui.rs` around lines 164 - 224, Define shared constants for
the window IDs "viewport", "timeline", "hierarchy", and "settings" in the
appropriate UI module scope, then replace the literals in both the dock
descriptor registrations and setup_editor_ui with those constants. Ensure
WindowRegistry registration and dock leaf references use the same symbols.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@editor/moxie_ui/src/glass/glass.wgsl`:
- Line 46: Update the shape calculation around the WGSL `shape` expression to
use a hard mask when `extra.x` is zero, while retaining the existing smooth
feathering behavior for nonzero values. Ensure the zero-feather path does not
force a one-pixel feather through `max(extra.x, 1.0)`.

In `@editor/moxie_ui/src/lib.rs`:
- Around line 33-50: Update MoxieUiPlugin’s documentation to remove the claim
that DockPlugin pulls in GlassPlugin, since GlassPlugin is registered explicitly
in the plugin tuple. Keep the existing GlassPlugin registration unchanged.

In `@editor/moxie_ui/src/reactive.rs`:
- Around line 250-261: Update component_changed so an absent C returns false on
the initial poll and remains treated as unchanged until a component value is
present, rather than delegating directly to value_changed with Option<C>.
Preserve change detection for inserted or modified C values and use the existing
world.get::<C> lookup.

In `@editor/moxie_ui/src/widgets/dock/drag.rs`:
- Around line 88-113: Update on_tab_drag_start to return immediately when
drag_state is already in an active drag state, especially
DockDragState::Dragging, before resolving the tab or assigning PendingDrag.
Preserve the existing initialization for idle or pending states as appropriate,
ensuring an in-flight drag’s ghost_entity, overlay_entity, and hidden source tab
remain intact.

In `@editor/moxie_ui/src/widgets/dock/split.rs`:
- Around line 207-230: Update on_handle_drag_end to clear override_cursor before
looking up the parent Node, so cleanup still occurs when the parent is missing
or reconfigured. Remove the dependency on the current flex_direction and clear
the override whenever it contains either supported resize cursor icon, while
preserving the existing drag-marker cleanup.

In `@editor/moxie_ui/src/widgets/inspector.rs`:
- Around line 361-379: Update the label construction in the inspector row around
Field::new and collect_leaves so it uses the final reflect-path segment, rather
than the full raw path. When the path is empty, fall back to the target’s
reflected type name; preserve readable nested labels instead of rendering bare
or empty text.

In `@editor/moxie_ui/src/widgets/inspector/widget.rs`:
- Around line 197-200: Update the u64 conversion entry so its outbound to_input
closure saturates values above i64::MAX instead of casting and wrapping;
preserve the existing inbound nonnegative clamp, ensuring both directions
saturate and u64::MAX displays as i64::MAX without losing the target value on
round trip.
- Around line 87-118: Remove the immediate Checked insert/remove operations from
the ValueChange observer and let bind_raw synchronize the checkbox after a
successful field.set, keeping the observer focused on queuing the write. Ensure
the queued write targets the Checkbox root node by using node rather than
change.source where the entity is required, and update the misleading
controlled-state comment accordingly.

In `@editor/moxie/src/ui.rs`:
- Around line 79-106: Document in the reflected EditorSettings definitions that
hdr and physical_size are restart-only settings because setup_editor_ui reads
them only during startup. Ensure the Settings UI communicates that runtime
changes are not applied, without altering setup_editor_ui or adding live-update
handling.

In `@editor/moxie/src/ui/timeline.rs`:
- Around line 262-281: Update on_divider_drag to base the panel width on the
pointer’s absolute drag distance rather than accumulating drag.delta.x. Add the
corresponding DragStart observer/state alongside the existing observer to record
the initial width, then apply drag.distance.x to that starting width before
clamping to NAME_PANEL_MIN and NAME_PANEL_MAX, matching handle_panel_drag’s
approach.

---

Nitpick comments:
In `@editor/moxie_ui/src/elements/timeline_track.rs`:
- Around line 3-11: Update the documentation for the timeline track to state
that the consuming application owns the PIXELS_PER_SECOND scale, while this
element receives only the resolved pixel width through
TimelineTrackProps::width. Keep the existing description of sizing and
pointer-based scrubbing intact.

In `@editor/moxie_ui/src/lib.rs`:
- Around line 1-4: Update the crate-level documentation module list to include
the public icons module declared by pub mod icons, alongside elements, widgets,
glass, reactive, and theme.

In `@editor/moxie_ui/src/widgets/dock/add_popup.rs`:
- Around line 26-32: Make the single-overlay invariant explicit at every handler
querying AddWindowPopupState, including on_add_click and the handlers around the
referenced lines: handle q_state.single_mut() errors with a debug assertion or
warning so zero or multiple matches are observable, or scope each query to the
owning overlay entity consistently with build_popup and ui.parent().

In `@editor/moxie_ui/src/widgets/dock/drag.rs`:
- Around line 450-460: Extract the shared drag cleanup from on_drag_end and
cancel_drag_on_escape into a teardown_drag helper that accepts Commands,
OverrideCursor, source_tab, ghost_entity, and optional overlay_entity. Move the
four cleanup operations—clear the cursor, despawn the ghost, restore source_tab
visibility, and despawn the overlay—into the helper, then call it from both
handlers while leaving their distinct drop or cancellation logic unchanged.
- Around line 700-733: Rename the nested helper function inside the outer
is_far_side to a distinct descriptive name, and update all four FlexDirection
match-arm call sites to use it. Leave the outer is_far_side signature and its
direction-handling behavior unchanged.
- Around line 210-217: Update on_drag_move to reuse the existing overlay entity
throughout the drag instead of despawning old_overlay and creating a replacement
on each event. When the drop target changes, update the overlay’s Node fields
(left, top, width, and height) and Visibility in place, while preserving the
existing overlay entity and ghost_node update flow.

In `@editor/moxie_ui/src/widgets/dock/tree.rs`:
- Around line 770-1012: Add tests covering the three untested public operations:
verify remove_window_kind removes every matching tab and simplifies drained
leaves/splits, verify insert_tab with an explicit index inserts into a different
destination leaf at the requested position and activates the tab, and verify
iter_dfs visits the tree nodes in depth-first order. Add these tests alongside
the existing DockTree tests, reusing helpers such as window_ids and tab_id_for.

In `@editor/moxie/src/ui.rs`:
- Around line 164-224: Define shared constants for the window IDs "viewport",
"timeline", "hierarchy", and "settings" in the appropriate UI module scope, then
replace the literals in both the dock descriptor registrations and
setup_editor_ui with those constants. Ensure WindowRegistry registration and
dock leaf references use the same symbols.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 3b3883e4-6963-4dd2-a940-ad417392fa64

📥 Commits

Reviewing files that changed from the base of the PR and between 09a218d and 049fd98.

📒 Files selected for processing (34)
  • editor/moxie/src/lib.rs
  • editor/moxie/src/playback.rs
  • editor/moxie/src/scene.rs
  • editor/moxie/src/ui.rs
  • editor/moxie/src/ui/hierarchy.rs
  • editor/moxie/src/ui/timeline.rs
  • editor/moxie/src/view.rs
  • editor/moxie_ui/examples/dock_demo.rs
  • editor/moxie_ui/src/elements.rs
  • editor/moxie_ui/src/elements/divider.rs
  • editor/moxie_ui/src/elements/frame.rs
  • editor/moxie_ui/src/elements/ghost_button.rs
  • editor/moxie_ui/src/elements/label.rs
  • editor/moxie_ui/src/elements/playhead.rs
  • editor/moxie_ui/src/elements/timeline_track.rs
  • editor/moxie_ui/src/glass/glass.wgsl
  • editor/moxie_ui/src/glass/material.rs
  • editor/moxie_ui/src/glass/mod.rs
  • editor/moxie_ui/src/icons.rs
  • editor/moxie_ui/src/lib.rs
  • editor/moxie_ui/src/reactive.rs
  • editor/moxie_ui/src/widgets.rs
  • editor/moxie_ui/src/widgets/dock.rs
  • editor/moxie_ui/src/widgets/dock/add_popup.rs
  • editor/moxie_ui/src/widgets/dock/area.rs
  • editor/moxie_ui/src/widgets/dock/drag.rs
  • editor/moxie_ui/src/widgets/dock/reconcile.rs
  • editor/moxie_ui/src/widgets/dock/registry.rs
  • editor/moxie_ui/src/widgets/dock/split.rs
  • editor/moxie_ui/src/widgets/dock/tabs.rs
  • editor/moxie_ui/src/widgets/dock/tree.rs
  • editor/moxie_ui/src/widgets/glass_backdrop.rs
  • editor/moxie_ui/src/widgets/inspector.rs
  • editor/moxie_ui/src/widgets/inspector/widget.rs
💤 Files with no reviewable changes (2)
  • editor/moxie_ui/src/glass/mod.rs
  • editor/moxie/src/scene.rs
🛑 Comments failed to post (6)
editor/moxie_ui/src/glass/glass.wgsl (1)

46-46: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Honor the zero-feather setting.

extra.x == 0.0 still creates a soft edge because max(extra.x, 1.0) forces a one-pixel feather. Use a hard mask when extra.x is zero. This preserves the documented crisp-edge mode.

Proposed fix
-    let shape = 1.0 - smoothstep(-max(extra.x, 1.0), 0.5, d);
+    let shape = if extra.x > 0.0 {
+        1.0 - smoothstep(-extra.x, 0.0, d)
+    } else {
+        1.0 - step(0.0, d)
+    };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@editor/moxie_ui/src/glass/glass.wgsl` at line 46, Update the shape
calculation around the WGSL `shape` expression to use a hard mask when `extra.x`
is zero, while retaining the existing smooth feathering behavior for nonzero
values. Ensure the zero-feather path does not force a one-pixel feather through
`max(extra.x, 1.0)`.
editor/moxie_ui/src/widgets/dock/drag.rs (1)

88-113: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Guard on_tab_drag_start against overwriting an active drag.

on_tab_drag_start assigns *drag_state = DockDragState::PendingDrag { .. } unconditionally. If a Pointer<DragStart> arrives while the state is already Dragging (a second pointer, a multi-touch drag, or a DragStart without a matching DragEnd), the previous ghost_entity and overlay_entity are dropped without a despawn, and the previous source_tab keeps Visibility::Hidden.

The result is a leaked ghost entity, a leaked overlay entity, and a tab that stays invisible until the leaf is rebuilt. The dock supports exactly one drag at a time, so ignore the new DragStart while a drag is in flight.

🐛 Proposed fix
     let entity = trigger.event_target();
     let Ok(tab) = tabs.get(entity) else { return };
 
+    // One drag at a time: a second pointer must not orphan the
+    // in-flight ghost/overlay or leave `source_tab` hidden.
+    if !matches!(*drag_state, DockDragState::Idle) {
+        return;
+    }
+
     let display_name = registry
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

fn on_tab_drag_start(
    trigger: On<Pointer<DragStart>>,
    tabs: Query<&super::area::DockTab>,
    mut drag_state: ResMut<DockDragState>,
    registry: Res<WindowRegistry>,
    ui_scale: Res<UiScale>,
) {
    let entity = trigger.event_target();
    let Ok(tab) = tabs.get(entity) else { return };

    // One drag at a time: a second pointer must not orphan the
    // in-flight ghost/overlay or leave `source_tab` hidden.
    if !matches!(*drag_state, DockDragState::Idle) {
        return;
    }

    let display_name = registry
        .get(&tab.window_id)
        .map(|d| d.name.clone())
        .unwrap_or_else(|| tab.window_id.clone());

    *drag_state = DockDragState::PendingDrag {
        source_tab: entity,
        tab_id: tab.tab_id,
        window_id: tab.window_id.clone(),
        window_name: display_name,
        start_pos: Vec2::new(
            trigger.event().pointer_location.position.x,
            trigger.event().pointer_location.position.y,
        ) / ui_scale.0,
    };
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@editor/moxie_ui/src/widgets/dock/drag.rs` around lines 88 - 113, Update
on_tab_drag_start to return immediately when drag_state is already in an active
drag state, especially DockDragState::Dragging, before resolving the tab or
assigning PendingDrag. Preserve the existing initialization for idle or pending
states as appropriate, ensuring an in-flight drag’s ghost_entity,
overlay_entity, and hidden source tab remain intact.
editor/moxie_ui/src/widgets/dock/split.rs (1)

207-230: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Clear the cursor override before the Node lookup, and do not depend on the flex direction being unchanged.

on_handle_drag_end returns at Line 223 if the parent has no Node. It also recomputes cursor_icon from the parent's current flex_direction at Line 226. If the parent is despawned, loses its Node, or is re-oriented by the dock reconciler during the drag, the equality check at Line 227 fails and override_cursor keeps the resize icon for the rest of the session. No later code clears it, because on_handle_drag_start only sets the override when it is None.

Clear the override when it holds either resize icon.

🐛 Proposed fix
 fn on_handle_drag_end(
     trigger: On<Pointer<DragEnd>>,
     handles: Query<&ChildOf, With<PanelHandle>>,
-    nodes: Query<&Node>,
     mut override_cursor: ResMut<OverrideCursor>,
     mut commands: Commands,
 ) {
     let handle = trigger.event_target();
-    let Ok(&ChildOf(parent)) = handles.get(handle) else {
+    if handles.get(handle).is_err() {
         return;
-    };
+    }
     // End of drag: drop the marker and clear the highlight.
     commands
         .entity(handle)
         .remove::<HandleDragging>()
         .insert(BackgroundColor(Color::NONE));
-    let Ok(node) = nodes.get(parent) else {
-        return;
-    };
-    let cursor_icon = get_drag_icon(node.flex_direction);
-    if override_cursor.0 == Some(EntityCursor::System(cursor_icon)) {
+    // Either resize icon: the group may have been re-oriented mid-drag.
+    let owned = matches!(
+        override_cursor.0,
+        Some(EntityCursor::System(
+            SystemCursorIcon::ColResize | SystemCursorIcon::RowResize
+        ))
+    );
+    if owned {
         override_cursor.0 = None;
     }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

fn on_handle_drag_end(
    trigger: On<Pointer<DragEnd>>,
    handles: Query<&ChildOf, With<PanelHandle>>,
    mut override_cursor: ResMut<OverrideCursor>,
    mut commands: Commands,
) {
    let handle = trigger.event_target();
    if handles.get(handle).is_err() {
        return;
    }
    // End of drag: drop the marker and clear the highlight.
    commands
        .entity(handle)
        .remove::<HandleDragging>()
        .insert(BackgroundColor(Color::NONE));
    // Either resize icon: the group may have been re-oriented mid-drag.
    let owned = matches!(
        override_cursor.0,
        Some(EntityCursor::System(
            SystemCursorIcon::ColResize | SystemCursorIcon::RowResize
        ))
    );
    if owned {
        override_cursor.0 = None;
    }
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@editor/moxie_ui/src/widgets/dock/split.rs` around lines 207 - 230, Update
on_handle_drag_end to clear override_cursor before looking up the parent Node,
so cleanup still occurs when the parent is missing or reconfigured. Remove the
dependency on the current flex_direction and clear the override whenever it
contains either supported resize cursor icon, while preserving the existing
drag-marker cleanup.
editor/moxie_ui/src/widgets/inspector.rs (1)

361-379: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Fall back to a readable label when the reflect path is empty.

label is the raw reflect path. collect_leaves pushes path: prefix.to_string() at Line 243, and prefix is "" when the target's own type has a registered ReflectInspect. The row then renders an empty Text, so the user sees a widget with no name. A nested tuple field also renders as a bare index, for example 0.

Use the last path segment, and fall back to the type name when the path is empty.

🐛 Proposed fix for the empty label
         let field = Field::new(target, leaf.path.clone());
-        let label = leaf.path;
+        let label = leaf
+            .path
+            .rsplit('.')
+            .next()
+            .filter(|segment| !segment.is_empty())
+            .map(str::to_string)
+            .unwrap_or_else(|| "value".to_string());
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

        let field = Field::new(target, leaf.path.clone());
        let label = leaf
            .path
            .rsplit('.')
            .next()
            .filter(|segment| !segment.is_empty())
            .map(str::to_string)
            .unwrap_or_else(|| "value".to_string());
        ui.bsn(bsn! {
            Node {
                width: Val::Percent(100.0),
                flex_direction: FlexDirection::Row,
                justify_content: JustifyContent::SpaceBetween,
                align_items: AlignItems::Center,
                column_gap: Val::Px(8.0),
                padding: UiRect::vertical(Val::Px(2.0)),
            }
        })
        .with(move |ui| {
            ui.bsn(bsn! {
                Text({label})
                ThemedText
                TextFont { font_size: FontSize::Px(12.0) }
            });
            drawer.build(&field, ui);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@editor/moxie_ui/src/widgets/inspector.rs` around lines 361 - 379, Update the
label construction in the inspector row around Field::new and collect_leaves so
it uses the final reflect-path segment, rather than the full raw path. When the
path is empty, fall back to the target’s reflected type name; preserve readable
nested labels instead of rendering bare or empty text.
editor/moxie_ui/src/widgets/inspector/widget.rs (2)

87-118: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Inspect glass_checkbox to see which entity emits ValueChange.
fd -e rs | xargs rg -n -A25 'fn glass_checkbox'
rg -n -C3 'ValueChange|Checked' --type=rs -g '!**/inspector/**'

Repository: voxell-tech/motiongfx

Length of output: 2016


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== files =="
git ls-files '*.rs' | sed -n '1,120p'

echo "== locate glass checkbox and checked/value change =="
rg -n -C4 'glass_checkbox|GlassCheckMark|Checked|ValueChange|impl Scene for Checkbox|entity_mut\(.*\.observe|observe\(' editor || true

echo "== relevant widget files =="
for f in editor/moxie_ui/src/widgets/inspector/widget.rs editor/moxie_ui/src/glass/widget.rs editor/moxie_ui/src/inspector.rs; do
  if [ -f "$f" ]; then
    echo "--- $f ---"
    wc -l "$f"
    sed -n '1,240p' "$f"
  fi
done

Repository: voxell-tech/motiongfx

Length of output: 26777


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== all files =="
git ls-files | sed -n '1,120p'

echo "== locate glass checkbox and checked/value change =="
rg -n -C4 'glass_checkbox|GlassCheckMark|Checked|ValueChange|impl Scene for Checkbox|entity_mut\(.*\.observe|observe\(' . || true

echo "== relevant widget files =="
for f in editor/moxie_ui/src/widgets/inspector/widget.rs editor/moxie_ui/src/glass/widget.rs editor/moxie_ui/src/inspector.rs; do
  if [ -f "$f" ]; then
    echo "--- $f ---"
    wc -l "$f"
    sed -n '1,260p' "$f"
  fi
done

Repository: voxell-tech/motiongfx

Length of output: 28329


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Cargo lock bevy version =="
python3 - <<'PY'
from pathlib import Path
text = Path("Cargo.lock").read_text()
for block in text.split("\n\n"):
    if "name = \"bevy\"" in block:
        print(block)
        break
PY

echo "== Field implementation search =="
rg -n -C5 'struct Field|impl Field|fn set\(.*World|fn set\(&self|fn target\(|target_changed|class Field|deref.*Field|marked_changed|Changed' editor/moxie_ui/src editor/moxie/src || true

echo "== inspector.rs lines =="
if [ -f editor/moxie_ui/src/widgets/inspector.rs ]; then
  wc -l editor/moxie_ui/src/widgets/inspector.rs
  sed -n '1,260p' editor/moxie_ui/src/widgets/inspector.rs
fi

Repository: voxell-tech/motiongfx

Length of output: 18899


Remove the optimistic Checked write, or correct the comment.

The comment says the checkbox state only moves after the write lands, but the observer inserts/removes Checked immediately after queuing field.set. If Field::set cannot resolve the path or the value type mismatches, it only logs a warning, so the checkbox can show a value the inspector target does not hold. The bind_raw binding already keeps Checked in sync after a successful write, which is consistent with number_field.

Use node for the write too if Checked is owned by the Checkbox root; change.source may be the toggle descendant.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@editor/moxie_ui/src/widgets/inspector/widget.rs` around lines 87 - 118,
Remove the immediate Checked insert/remove operations from the ValueChange
observer and let bind_raw synchronize the checkbox after a successful field.set,
keeping the observer focused on queuing the write. Ensure the queued write
targets the Checkbox root node by using node rather than change.source where the
entity is required, and update the misleading controlled-state comment
accordingly.

197-200: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Clamp the u64 value on the way into the input.

The comment states that these types clamp so that as does not wrap or truncate. Only the inbound direction to_field clamps. The outbound direction to_input for u64 is |value| value as i64, which wraps for any value above i64::MAX. u64::MAX then displays as -1. If the user commits that displayed value, to_field maps -1 to 0 and the target loses its value.

Saturate in both directions.

🐛 Proposed fix for the `u64` round trip
-    u64 => I64, I64, i64, |value: i64| value.max(0) as u64, |value| value as i64;
+    u64 => I64, I64, i64, |value: i64| value.max(0) as u64, |value: u64| value.min(i64::MAX as u64) as i64;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

    // There is no unsigned input format, so these ride an `i64` and
    // clamp on the way back - `as` alone would wrap or truncate.
    u32 => I64, I64, i64, |value: i64| value.clamp(0, u32::MAX as i64) as u32, |value| value as i64;
    u64 => I64, I64, i64, |value: i64| value.max(0) as u64, |value: u64| value.min(i64::MAX as u64) as i64;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@editor/moxie_ui/src/widgets/inspector/widget.rs` around lines 197 - 200,
Update the u64 conversion entry so its outbound to_input closure saturates
values above i64::MAX instead of casting and wrapping; preserve the existing
inbound nonnegative clamp, ensuring both directions saturate and u64::MAX
displays as i64::MAX without losing the target value on round trip.

@nixonyh
nixonyh force-pushed the nixon/element-widget-split branch from 049fd98 to 8740393 Compare August 5, 2026 09:12
@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@editor/moxie_ui/src/widgets/inspector/widget.rs`:
- Line 200: Update the u64 registration in the widget control mapping to avoid
converting through i64, which corrupts values above i64::MAX. Use an unsigned
decimal control that parses and preserves the full u64 range; otherwise remove
the u64 registration until such support exists, while retaining the existing
nonnegative edit behavior only where the type is safely supported.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 9b7e3afe-8dce-4092-b516-df8535d5354b

📥 Commits

Reviewing files that changed from the base of the PR and between 09a218d and 8740393.

📒 Files selected for processing (34)
  • editor/moxie/src/lib.rs
  • editor/moxie/src/playback.rs
  • editor/moxie/src/scene.rs
  • editor/moxie/src/ui.rs
  • editor/moxie/src/ui/hierarchy.rs
  • editor/moxie/src/ui/timeline.rs
  • editor/moxie/src/view.rs
  • editor/moxie_ui/examples/dock_demo.rs
  • editor/moxie_ui/src/elements.rs
  • editor/moxie_ui/src/elements/divider.rs
  • editor/moxie_ui/src/elements/frame.rs
  • editor/moxie_ui/src/elements/ghost_button.rs
  • editor/moxie_ui/src/elements/label.rs
  • editor/moxie_ui/src/elements/playhead.rs
  • editor/moxie_ui/src/elements/timeline_track.rs
  • editor/moxie_ui/src/glass/glass.wgsl
  • editor/moxie_ui/src/glass/material.rs
  • editor/moxie_ui/src/glass/mod.rs
  • editor/moxie_ui/src/icons.rs
  • editor/moxie_ui/src/lib.rs
  • editor/moxie_ui/src/reactive.rs
  • editor/moxie_ui/src/widgets.rs
  • editor/moxie_ui/src/widgets/dock.rs
  • editor/moxie_ui/src/widgets/dock/add_popup.rs
  • editor/moxie_ui/src/widgets/dock/area.rs
  • editor/moxie_ui/src/widgets/dock/drag.rs
  • editor/moxie_ui/src/widgets/dock/reconcile.rs
  • editor/moxie_ui/src/widgets/dock/registry.rs
  • editor/moxie_ui/src/widgets/dock/split.rs
  • editor/moxie_ui/src/widgets/dock/tabs.rs
  • editor/moxie_ui/src/widgets/dock/tree.rs
  • editor/moxie_ui/src/widgets/glass_backdrop.rs
  • editor/moxie_ui/src/widgets/inspector.rs
  • editor/moxie_ui/src/widgets/inspector/widget.rs
💤 Files with no reviewable changes (2)
  • editor/moxie_ui/src/glass/mod.rs
  • editor/moxie/src/scene.rs
🚧 Files skipped from review as they are similar to previous changes (28)
  • editor/moxie/src/playback.rs
  • editor/moxie_ui/src/widgets/glass_backdrop.rs
  • editor/moxie_ui/src/glass/material.rs
  • editor/moxie_ui/src/widgets/dock/reconcile.rs
  • editor/moxie_ui/src/elements/timeline_track.rs
  • editor/moxie_ui/src/widgets/dock.rs
  • editor/moxie_ui/src/elements/playhead.rs
  • editor/moxie/src/view.rs
  • editor/moxie_ui/src/icons.rs
  • editor/moxie_ui/src/widgets.rs
  • editor/moxie_ui/src/elements/divider.rs
  • editor/moxie_ui/src/glass/glass.wgsl
  • editor/moxie_ui/src/elements/frame.rs
  • editor/moxie_ui/src/widgets/dock/add_popup.rs
  • editor/moxie_ui/src/elements/ghost_button.rs
  • editor/moxie_ui/src/widgets/dock/tabs.rs
  • editor/moxie_ui/src/reactive.rs
  • editor/moxie_ui/examples/dock_demo.rs
  • editor/moxie/src/ui/hierarchy.rs
  • editor/moxie_ui/src/widgets/dock/split.rs
  • editor/moxie_ui/src/widgets/dock/drag.rs
  • editor/moxie_ui/src/elements/label.rs
  • editor/moxie/src/ui.rs
  • editor/moxie/src/lib.rs
  • editor/moxie_ui/src/widgets/dock/area.rs
  • editor/moxie_ui/src/widgets/inspector.rs
  • editor/moxie_ui/src/widgets/dock/tree.rs
  • editor/moxie/src/ui/timeline.rs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@editor/moxie_ui/src/widgets/inspector/widget.rs`:
- Line 200: Update the u64 registration in the widget control mapping to avoid
converting through i64, which corrupts values above i64::MAX. Use an unsigned
decimal control that parses and preserves the full u64 range; otherwise remove
the u64 registration until such support exists, while retaining the existing
nonnegative edit behavior only where the type is safely supported.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 9b7e3afe-8dce-4092-b516-df8535d5354b

📥 Commits

Reviewing files that changed from the base of the PR and between 09a218d and 8740393.

📒 Files selected for processing (34)
  • editor/moxie/src/lib.rs
  • editor/moxie/src/playback.rs
  • editor/moxie/src/scene.rs
  • editor/moxie/src/ui.rs
  • editor/moxie/src/ui/hierarchy.rs
  • editor/moxie/src/ui/timeline.rs
  • editor/moxie/src/view.rs
  • editor/moxie_ui/examples/dock_demo.rs
  • editor/moxie_ui/src/elements.rs
  • editor/moxie_ui/src/elements/divider.rs
  • editor/moxie_ui/src/elements/frame.rs
  • editor/moxie_ui/src/elements/ghost_button.rs
  • editor/moxie_ui/src/elements/label.rs
  • editor/moxie_ui/src/elements/playhead.rs
  • editor/moxie_ui/src/elements/timeline_track.rs
  • editor/moxie_ui/src/glass/glass.wgsl
  • editor/moxie_ui/src/glass/material.rs
  • editor/moxie_ui/src/glass/mod.rs
  • editor/moxie_ui/src/icons.rs
  • editor/moxie_ui/src/lib.rs
  • editor/moxie_ui/src/reactive.rs
  • editor/moxie_ui/src/widgets.rs
  • editor/moxie_ui/src/widgets/dock.rs
  • editor/moxie_ui/src/widgets/dock/add_popup.rs
  • editor/moxie_ui/src/widgets/dock/area.rs
  • editor/moxie_ui/src/widgets/dock/drag.rs
  • editor/moxie_ui/src/widgets/dock/reconcile.rs
  • editor/moxie_ui/src/widgets/dock/registry.rs
  • editor/moxie_ui/src/widgets/dock/split.rs
  • editor/moxie_ui/src/widgets/dock/tabs.rs
  • editor/moxie_ui/src/widgets/dock/tree.rs
  • editor/moxie_ui/src/widgets/glass_backdrop.rs
  • editor/moxie_ui/src/widgets/inspector.rs
  • editor/moxie_ui/src/widgets/inspector/widget.rs
💤 Files with no reviewable changes (2)
  • editor/moxie_ui/src/glass/mod.rs
  • editor/moxie/src/scene.rs
🚧 Files skipped from review as they are similar to previous changes (28)
  • editor/moxie/src/playback.rs
  • editor/moxie_ui/src/widgets/glass_backdrop.rs
  • editor/moxie_ui/src/glass/material.rs
  • editor/moxie_ui/src/widgets/dock/reconcile.rs
  • editor/moxie_ui/src/elements/timeline_track.rs
  • editor/moxie_ui/src/widgets/dock.rs
  • editor/moxie_ui/src/elements/playhead.rs
  • editor/moxie/src/view.rs
  • editor/moxie_ui/src/icons.rs
  • editor/moxie_ui/src/widgets.rs
  • editor/moxie_ui/src/elements/divider.rs
  • editor/moxie_ui/src/glass/glass.wgsl
  • editor/moxie_ui/src/elements/frame.rs
  • editor/moxie_ui/src/widgets/dock/add_popup.rs
  • editor/moxie_ui/src/elements/ghost_button.rs
  • editor/moxie_ui/src/widgets/dock/tabs.rs
  • editor/moxie_ui/src/reactive.rs
  • editor/moxie_ui/examples/dock_demo.rs
  • editor/moxie/src/ui/hierarchy.rs
  • editor/moxie_ui/src/widgets/dock/split.rs
  • editor/moxie_ui/src/widgets/dock/drag.rs
  • editor/moxie_ui/src/elements/label.rs
  • editor/moxie/src/ui.rs
  • editor/moxie/src/lib.rs
  • editor/moxie_ui/src/widgets/dock/area.rs
  • editor/moxie_ui/src/widgets/inspector.rs
  • editor/moxie_ui/src/widgets/dock/tree.rs
  • editor/moxie/src/ui/timeline.rs
🛑 Comments failed to post (1)
editor/moxie_ui/src/widgets/inspector/widget.rs (1)

200-200: 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Do not convert the full u64 domain through i64.

Line 200 converts values above i64::MAX to negative i64 values. For example, u64::MAX displays as -1. A subsequent edit can then write a different value through value.max(0) as u64.

Use an unsigned decimal control that parses u64, or do not register u64 until the control supports its full range.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@editor/moxie_ui/src/widgets/inspector/widget.rs` at line 200, Update the u64
registration in the widget control mapping to avoid converting through i64,
which corrupts values above i64::MAX. Use an unsigned decimal control that
parses and preserves the full u64 range; otherwise remove the u64 registration
until such support exists, while retaining the existing nonnegative edit
behavior only where the type is safely supported.

@Sheerwin02 Sheerwin02 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm!

@nixonyh
nixonyh merged commit 662dce8 into main Aug 5, 2026
8 checks passed
@nixonyh
nixonyh deleted the nixon/element-widget-split branch August 5, 2026 09:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants