git.lucas.co / cce-data-editor
structured data editor
git clone https://git.lucas.co/cce-data-editor.git

commitd06e22e6e8665a129717919680b4e637ec8a49e9
parent5cba0c1cff
authorLucas Galante <lsgalante12@gmail.com>
date2026-10-07 22:07
refactor: hold widgets in Owned boxes (cce-ui Owned<W>)

The UiContext registry keeps raw pointers to registered widgets, and a
widget that moved while registered (its Vec reallocating, its struct
returned by value) left one pointing at the old address. Each widget
field is now an Owned<Adapted<..>> — a heap box whose allocation, not the
widget, carries the liveness token the registry watches — so the
registered address never moves and is dropped from the registry when the
box is freed. Field access is unchanged through Deref; construction
sites wrap in Owned::new.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

 src/main.rs | 69 +++++++++++++++++++++++++++++++------------------------------
 1 file changed, 35 insertions(+), 34 deletions(-)

diff --git a/src/main.rs b/src/main.rs
index e938116..98944a8 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,4 +1,5 @@
 use wayland_client::QueueHandle;
+use cce_ui::widget::Owned;
 use cce_ui::cosmic_text::FontSystem;
 use cce_ui::engine::{Application, EngineState, LogicalPosition, LogicalSize, WindowSettings};
 use cce_ui::widget::{
@@ -425,7 +426,7 @@ struct DataEditorApp {
     keys: DataEditorKeys,
 
     // Toolbar Buttons
-    btn_open: cce_ui::widget::Adapted<Dropdown>,
+    btn_open: Owned<cce_ui::widget::Adapted<Dropdown>>,
 
     // Left Panel Form Edit
     flat_keys: Vec<(String, serde_json::Value)>,
@@ -433,28 +434,28 @@ struct DataEditorApp {
     /// A `--select <flat.path>` from the CLI, waiting for the first laid-out
     /// frame (the tree's viewport height) before it can select and scroll.
     pending_select: Option<String>,
-    tree_list: cce_ui::widget::Adapted<TreeList>,
+    tree_list: Owned<cce_ui::widget::Adapted<TreeList>>,
 
     // Edit Value input
-    selected_value_editor: cce_ui::widget::Adapted<TextBox>,
-    selected_color_editor: cce_ui::widget::Adapted<ColorSelector>,
-    selected_spinbox_editor: cce_ui::widget::Adapted<cce_ui::widget::Spinbox>,
-    selected_font_editor: cce_ui::widget::Adapted<FontSelector>,
-    selected_choice_editor: cce_ui::widget::Adapted<Dropdown>,
+    selected_value_editor: Owned<cce_ui::widget::Adapted<TextBox>>,
+    selected_color_editor: Owned<cce_ui::widget::Adapted<ColorSelector>>,
+    selected_spinbox_editor: Owned<cce_ui::widget::Adapted<cce_ui::widget::Spinbox>>,
+    selected_font_editor: Owned<cce_ui::widget::Adapted<FontSelector>>,
+    selected_choice_editor: Owned<cce_ui::widget::Adapted<Dropdown>>,
     /// A unit-bearing length — a `(mm)`/`(px)`/`(cm)`/`(in)`/`(pt)` annotation,
     /// or any string `cce_ui::units::Len` parses — edits as a two-decimal
     /// spinbox for the number beside a dropdown for the unit. Switching the
     /// unit keeps the LENGTH (converted through the process metric) and
     /// changes the number, so 2 mm shown in inches reads 0.08.
-    selected_len_editor: cce_ui::widget::Adapted<cce_ui::widget::Spinbox>,
-    selected_unit_editor: cce_ui::widget::Adapted<Dropdown>,
-    selected_keybind_editor: cce_ui::widget::Adapted<KeybindRecorder>,
-    selected_bool_editor: cce_ui::widget::Adapted<Checkbox>,
-    selected_button_editor: cce_ui::widget::Adapted<cce_ui::widget::Button>,
-    selected_bevel_editor: cce_ui::widget::Adapted<cce_ui::widget::BevelPreview>,
+    selected_len_editor: Owned<cce_ui::widget::Adapted<cce_ui::widget::Spinbox>>,
+    selected_unit_editor: Owned<cce_ui::widget::Adapted<Dropdown>>,
+    selected_keybind_editor: Owned<cce_ui::widget::Adapted<KeybindRecorder>>,
+    selected_bool_editor: Owned<cce_ui::widget::Adapted<Checkbox>>,
+    selected_button_editor: Owned<cce_ui::widget::Adapted<cce_ui::widget::Button>>,
+    selected_bevel_editor: Owned<cce_ui::widget::Adapted<cce_ui::widget::BevelPreview>>,
     /// The (ramp) type's inline preview — the spec's curve; clicking opens
     /// cce-ramp --key on the selected key.
-    selected_ramp_editor: cce_ui::widget::Adapted<cce_ui::widget::RampPreview>,
+    selected_ramp_editor: Owned<cce_ui::widget::Adapted<cce_ui::widget::RampPreview>>,
     /// A cce-relief child spawned from the (bevel) preview: kept so a second
     /// click refocuses it (try_wait reaps an exited one) instead of piling
     /// up editors.
@@ -463,7 +464,7 @@ struct DataEditorApp {
     ramp_child: Option<std::process::Child>,
 
     // Right Panel Raw Json
-    raw_json_editor: cce_ui::widget::Adapted<TextBox>,
+    raw_json_editor: Owned<cce_ui::widget::Adapted<TextBox>>,
 
     // App state
     current_file_path: Option<std::path::PathBuf>,
@@ -492,8 +493,8 @@ struct DataEditorApp {
     file_dialog_open: bool,
 
     // UI state
-    menubar: cce_ui::widget::Adapted<MenuBar>,
-    statusbar: cce_ui::widget::Adapted<StatusBar>,
+    menubar: Owned<cce_ui::widget::Adapted<MenuBar>>,
+    statusbar: Owned<cce_ui::widget::Adapted<StatusBar>>,
     width: u32,
     height: u32,
     scale_factor: f64,
@@ -1039,28 +1040,28 @@ impl Application for DataEditorApp {
 
             Self {
                 keys: DataEditorKeys::load(),
-                menubar,
-                statusbar,
-                btn_open,
+                menubar: Owned::new(menubar),
+                statusbar: Owned::new(statusbar),
+                btn_open: Owned::new(btn_open),
                 flat_keys,
                 selected_key_idx: None,
                 pending_select,
-                tree_list,
-                selected_value_editor,
-                selected_color_editor,
-                selected_spinbox_editor,
-                selected_font_editor,
-                selected_choice_editor,
-                selected_len_editor,
-                selected_unit_editor,
-                selected_keybind_editor,
-                selected_bool_editor,
-                selected_button_editor,
-                selected_bevel_editor,
-                selected_ramp_editor,
+                tree_list: Owned::new(tree_list),
+                selected_value_editor: Owned::new(selected_value_editor),
+                selected_color_editor: Owned::new(selected_color_editor),
+                selected_spinbox_editor: Owned::new(selected_spinbox_editor),
+                selected_font_editor: Owned::new(selected_font_editor),
+                selected_choice_editor: Owned::new(selected_choice_editor),
+                selected_len_editor: Owned::new(selected_len_editor),
+                selected_unit_editor: Owned::new(selected_unit_editor),
+                selected_keybind_editor: Owned::new(selected_keybind_editor),
+                selected_bool_editor: Owned::new(selected_bool_editor),
+                selected_button_editor: Owned::new(selected_button_editor),
+                selected_bevel_editor: Owned::new(selected_bevel_editor),
+                selected_ramp_editor: Owned::new(selected_ramp_editor),
                 bevel_child: None,
                 ramp_child: None,
-                raw_json_editor,
+                raw_json_editor: Owned::new(raw_json_editor),
                 current_file_path,
                 status_message: None,
                 overflow_now: 0,