diff --git a/src/app/mod.rs b/src/app/mod.rs index 37a73f74c..6f6baf27e 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -2671,11 +2671,11 @@ pub enum Message { ToggleXrefManager, /// Rescan the active drawing's references into the palette. XrefManagerRefresh, - /// Toggle one palette row (entry index) in the multi-selection set. - XrefManagerSelect(usize), + /// Toggle one palette row (stable row id) in the multi-selection set. + XrefManagerSelect(crate::ui::window::xref_manager::XrefRowId), /// Right-click on a palette row: single-select it when outside the /// selection (the context menu then acts on the selection). - XrefRowRightClick(usize), + XrefRowRightClick(crate::ui::window::xref_manager::XrefRowId), /// Flip the palette's list/tree presentation. XrefManagerToggleTree, /// Start a reference-table column divider drag (column index). @@ -2712,16 +2712,19 @@ pub enum Message { XrefManagerToggleExpand(u64), /// Selection-scoped palette operation (detach/unload/reload/overlay/pathtype). XrefManagerOp(crate::ui::window::xref_manager::XrefPaletteOp), - /// Row-scoped palette operation: selects row `index`, then applies the - /// operation to it. - XrefRowOp(usize, crate::ui::window::xref_manager::XrefPaletteOp), + /// Row-scoped palette operation: selects the row by stable id, then + /// applies the operation to it. + XrefRowOp( + crate::ui::window::xref_manager::XrefRowId, + crate::ui::window::xref_manager::XrefPaletteOp, + ), /// Prefill the command line for Find & Replace across references /// (`XREF Path Find `); the CLI parses and runs it. XrefFindReplacePrompt, - /// Pick a new path for a specific row index. - XrefRowPathPick(usize), - /// Prefill the command line for Find & Replace for a specific row index. - XrefRowFindReplacePrompt(usize), + /// Pick a new path for a specific row (stable id). + XrefRowPathPick(crate::ui::window::xref_manager::XrefRowId), + /// Prefill the command line for Find & Replace for a specific row id. + XrefRowFindReplacePrompt(crate::ui::window::xref_manager::XrefRowId), XrefRowChangePathEnter, XrefRowChangePathLeave, LayerToggleVisible(String), diff --git a/src/app/update/dialog.rs b/src/app/update/dialog.rs index 985e2c424..e1bcfc47c 100644 --- a/src/app/update/dialog.rs +++ b/src/app/update/dialog.rs @@ -1818,6 +1818,35 @@ mod tests { .join("\n") } + #[test] + fn xref_rowop_unknown_id_is_noop() { + // #49 RED: row messages carry (key, saved_path) identity and resolve + // at dispatch; a deleted row's id must no-op instead of hitting whatever + // row now sits at the old index. Fails to compile until the payload + // carries XrefRowId instead of usize. + use crate::app::Message; + use crate::ui::window::xref_manager::{XrefPaletteOp, XrefRowId}; + let mut app = fresh(); + let i = app.active_tab; + let start = app.command_line.history.len(); + let ghost = XrefRowId { + key: u64::MAX, + saved_path: "gone.dwg".to_string(), + }; + let records_before = app.tabs[i].scene.document.block_records.iter().count(); + let _ = app.update(Message::XrefRowOp(ghost, XrefPaletteOp::Detach)); + assert_eq!( + app.tabs[i].scene.document.block_records.iter().count(), + records_before, + "no-op must not disturb the document" + ); + let out = palette_output(&app, start); + assert!( + !out.contains("detached"), + "unknown id must not detach anything, got: {out:?}" + ); + } + #[test] fn palette_rowop_nested_reports_per_entry() { // Behavioral matrix: every row op on a NESTED row via the GUI @@ -1825,7 +1854,7 @@ mod tests { // wording), never silently skip. use codec::tables::BlockRecord; use crate::app::Message; - use crate::ui::window::xref_manager::XrefPaletteOp; + use crate::ui::window::xref_manager::{XrefPaletteOp, XrefRowId}; let dir = palette_tmpdir("nestedmatrix"); let mut host_doc = codec::CadDocument::new(); let mut inner = BlockRecord::new("INNER"); @@ -1843,8 +1872,9 @@ mod tests { app.tabs[i].scene.document.block_records.add(br).unwrap(); app.tabs[i].current_path = Some(dir.join("app.dwg")); app.refresh_xref_manager(); - let idx = app.xref_manager.entries.iter().position(|e| e.name == "INNER").expect("nested INNER listed"); - assert!(app.xref_manager.entries[idx].parent_key.is_some()); + let row = app.xref_manager.entries.iter().find(|e| e.name == "INNER").expect("nested INNER listed"); + assert!(row.parent_key.is_some()); + let row_id = XrefRowId::of(row.key, &row.saved_path); for op in [ XrefPaletteOp::Detach, XrefPaletteOp::Unload, @@ -1853,7 +1883,7 @@ mod tests { XrefPaletteOp::Overlay, ] { let start = app.command_line.history.len(); - let _ = app.update(Message::XrefRowOp(idx, op)); + let _ = app.update(Message::XrefRowOp(row_id.clone(), op)); let out = palette_output(&app, start); assert!(out.contains("INNER"), "op {op:?} on nested row gave: {out:?}"); assert!( @@ -1870,7 +1900,7 @@ mod tests { // drive the exact GUI message path (row right-click menu item). use codec::tables::BlockRecord; use crate::app::Message; - use crate::ui::window::xref_manager::XrefPaletteOp; + use crate::ui::window::xref_manager::{XrefPaletteOp, XrefRowId}; let mut app = fresh(); let i = app.active_tab; for name in ["PLAN", "SITE"] { @@ -1882,15 +1912,17 @@ mod tests { } app.tabs[i].current_path = Some(std::env::temp_dir().join("ocs_repro_host.dwg")); app.refresh_xref_manager(); - let idx = app.xref_manager.entries.iter().position(|e| e.name == "PLAN").expect("PLAN listed"); + let plan = app.xref_manager.entries.iter().find(|e| e.name == "PLAN").expect("PLAN listed").clone(); + let plan_id = XrefRowId::of(plan.key, &plan.saved_path); let start = app.command_line.history.len(); - let _ = app.update(Message::XrefRowOp(idx, XrefPaletteOp::Detach)); + let _ = app.update(Message::XrefRowOp(plan_id, XrefPaletteOp::Detach)); let out = palette_output(&app, start); assert!(out.contains("PLAN"), "detach output missing, got: {out:?}"); assert!(app.tabs[i].scene.document.block_records.get("PLAN").is_none(), "PLAN definition must be gone"); - let idx = app.xref_manager.entries.iter().position(|e| e.name == "SITE").expect("SITE listed"); + let site = app.xref_manager.entries.iter().find(|e| e.name == "SITE").expect("SITE listed").clone(); + let site_id = XrefRowId::of(site.key, &site.saved_path); let start = app.command_line.history.len(); - let _ = app.update(Message::XrefRowOp(idx, XrefPaletteOp::Unload)); + let _ = app.update(Message::XrefRowOp(site_id, XrefPaletteOp::Unload)); let out = palette_output(&app, start); assert!(out.contains("SITE"), "unload output missing, got: {out:?}"); assert_eq!( @@ -1906,7 +1938,7 @@ mod tests { use codec::tables::BlockRecord; use crate::app::Message; use crate::io::xref_model::{Pathtype, RefType}; - use crate::ui::window::xref_manager::XrefPaletteOp; + use crate::ui::window::xref_manager::{XrefPaletteOp, XrefRowId}; let mut app = fresh(); let i = app.active_tab; let mut br = BlockRecord::new("PLAN"); @@ -1916,16 +1948,17 @@ mod tests { app.tabs[i].scene.document.block_records.add(br).unwrap(); app.tabs[i].current_path = Some(std::env::temp_dir().join("ocs_rowop_host.dwg")); app.refresh_xref_manager(); - let idx = app.xref_manager.entries.iter().position(|e| e.name == "PLAN").expect("PLAN listed"); + let plan = app.xref_manager.entries.iter().find(|e| e.name == "PLAN").expect("PLAN listed").clone(); + let plan_id = XrefRowId::of(plan.key, &plan.saved_path); let start = app.command_line.history.len(); - let _ = app.update(Message::XrefRowOp(idx, XrefPaletteOp::Overlay)); + let _ = app.update(Message::XrefRowOp(plan_id.clone(), XrefPaletteOp::Overlay)); let out = palette_output(&app, start); assert!(out.contains("PLAN"), "got: {out:?}"); let br = app.tabs[i].scene.document.block_records.get("PLAN").unwrap(); assert!(br.flags.is_xref_overlay && !br.flags.is_xref); assert_eq!(app.xref_manager.entries.iter().find(|e| e.name == "PLAN").unwrap().ref_type, RefType::Overlay); let start = app.command_line.history.len(); - let _ = app.update(Message::XrefRowOp(idx, XrefPaletteOp::Pathtype(Pathtype::None))); + let _ = app.update(Message::XrefRowOp(plan_id, XrefPaletteOp::Pathtype(Pathtype::None))); let out = palette_output(&app, start); assert!(out.contains("PLAN"), "got: {out:?}"); let br = app.tabs[i].scene.document.block_records.get("PLAN").unwrap(); diff --git a/src/app/update/mod.rs b/src/app/update/mod.rs index 334b0b49b..ed633bf42 100644 --- a/src/app/update/mod.rs +++ b/src/app/update/mod.rs @@ -2613,7 +2613,7 @@ impl OpenCADStudio { self.refresh_xref_manager(); Task::none() } - Message::XrefManagerSelect(index) => { + Message::XrefManagerSelect(id) => { use crate::ui::window::xref_manager::SelectExtend; // Normal GUI list behavior off the globally tracked // modifiers (same source as the layer list): plain click @@ -2625,12 +2625,12 @@ impl OpenCADStudio { } else { SelectExtend::Single }; - self.xref_manager.click_select(index, extend); + self.xref_manager.click_select(&id, extend); Task::none() } - Message::XrefRowRightClick(index) => { + Message::XrefRowRightClick(id) => { self.xref_manager.row_change_path_open = false; - self.xref_manager.right_click_select(index); + self.xref_manager.right_click_select(&id); Task::none() } Message::XrefManagerToggleTree => { @@ -2775,62 +2775,69 @@ impl OpenCADStudio { self.xref_manager_op(op); Task::none() } - Message::XrefRowOp(index, op) => { + Message::XrefRowOp(id, op) => { // Open is not a palette op — it navigates to the file. if op == crate::ui::window::xref_manager::XrefPaletteOp::Open { - self.xref_manager.right_click_select(index); + self.xref_manager.right_click_select(&id); self.xref_manager.row_change_path_open = false; - if let Some(entry) = self.xref_manager.entries.get(index) { - if let Some(found) = entry.found_at.clone() { - let is_dwg = found.to_ascii_lowercase().ends_with(".dwg") - || found.to_ascii_lowercase().ends_with(".dxf"); - self.command_line.push_output(crate::tf!("XOPEN: opening \"{}\".", found).as_ref()); - if is_dwg { - return self.update(Message::OpenRecent(std::path::PathBuf::from(found))); + if let Some(index) = self.xref_manager.row_index(&id) { + if let Some(entry) = self.xref_manager.entries.get(index) { + if let Some(found) = entry.found_at.clone() { + let is_dwg = found.to_ascii_lowercase().ends_with(".dwg") + || found.to_ascii_lowercase().ends_with(".dxf"); + self.command_line.push_output(crate::tf!("XOPEN: opening \"{}\".", found).as_ref()); + if is_dwg { + return self.update(Message::OpenRecent(std::path::PathBuf::from(found))); + } else { + #[cfg(not(target_arch = "wasm32"))] + let _ = open::that_detached(&found); + } } else { - #[cfg(not(target_arch = "wasm32"))] - let _ = open::that_detached(&found); + self.command_line.push_error( + crate::t!("File not found — check the saved path.").as_ref(), + ); } - } else { - self.command_line.push_error( - crate::t!("File not found — check the saved path.").as_ref(), - ); } } return Task::none(); } // Attach prompts the file dialog to pick a drawing to attach. if matches!(op, crate::ui::window::xref_manager::XrefPaletteOp::Attach) { - self.xref_manager.right_click_select(index); + self.xref_manager.right_click_select(&id); self.xref_manager.row_change_path_open = false; self.command_line.push_output(crate::t!("XATTACH").as_ref()); return self.update(Message::XAttachPick); } // Row-scoped op: select the row first, then run the op. - self.xref_manager.right_click_select(index); + self.xref_manager.right_click_select(&id); self.xref_manager.row_change_path_open = false; self.xref_manager_op(op); Task::none() } - Message::XrefRowPathPick(index) => { - self.xref_manager.right_click_select(index); + Message::XrefRowPathPick(id) => { + self.xref_manager.right_click_select(&id); self.xref_manager.row_change_path_open = false; self.update(Message::XrefPathPick) } - Message::XrefRowFindReplacePrompt(index) => { - self.xref_manager.right_click_select(index); + Message::XrefRowFindReplacePrompt(id) => { + self.xref_manager.right_click_select(&id); self.xref_manager.row_change_path_open = false; - let prefill = if let Some(entry) = self.xref_manager.entries.get(index) { - let saved = &entry.saved_path; - if let Some(parent) = std::path::Path::new(saved).parent().and_then(|p| p.to_str()) { - if !parent.is_empty() { - format!("XREF Path Find \"{}\" ", parent) - } else { - "XREF Path Find ".to_string() - } - } else { - "XREF Path Find ".to_string() - } + let prefill = if let Some(index) = self.xref_manager.row_index(&id) { + self.xref_manager.entries.get(index).map_or_else( + || "XREF Path Find ".to_string(), + |entry| { + let saved = &entry.saved_path; + if let Some(parent) = std::path::Path::new(saved).parent().and_then(|p| p.to_str()) { + if !parent.is_empty() { + format!("XREF Path Find \"{}\" ", parent) + } else { + "XREF Path Find ".to_string() + } + } else { + "XREF Path Find ".to_string() + } + }, + ) } else { "XREF Path Find ".to_string() }; diff --git a/src/ui/window/xref_manager.rs b/src/ui/window/xref_manager.rs index af9d20634..ac8afdb98 100644 --- a/src/ui/window/xref_manager.rs +++ b/src/ui/window/xref_manager.rs @@ -196,6 +196,25 @@ pub enum XrefPaletteOp { Pathtype(Pathtype), } +/// Stable row identity carried by row messages instead of the positional +/// display index (#49): a rescan/reorder between view and dispatch must not +/// retarget the row. Same `(key, saved_path)` pair `refresh` uses to preserve +/// selection (key alone can collide across host/nested scopes). +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct XrefRowId { + pub key: u64, + pub saved_path: String, +} + +impl XrefRowId { + pub fn of(key: u64, saved_path: &str) -> Self { + Self { + key, + saved_path: saved_path.to_string(), + } + } +} + impl XrefManagerPanel { /// Rebuild `entries` from `doc` via [`collect_entries_with_prev`] and /// recompute the parent → children linkage for tree mode. Selection/anchor @@ -310,13 +329,24 @@ impl XrefManagerPanel { .collect() } + /// Resolve a row id to its current display index (entries reorder on + /// every rescan). `None` = row gone meanwhile; callers no-op (#49). + pub fn row_index(&self, id: &XrefRowId) -> Option { + self.entries + .iter() + .position(|e| e.key == id.key && e.saved_path == id.saved_path) + } + /// Click handling for row picks with normal GUI list semantics: /// - plain click selects exactly one row (previous selection clears); /// - Ctrl/Cmd-click toggles one row; /// - Shift-click extends a contiguous range from the anchor (or the /// clicked row when there is no anchor yet). /// Tree mode always single-selects per spec, whatever the modifiers. - pub fn click_select(&mut self, index: usize, extend: SelectExtend) { + pub fn click_select(&mut self, id: &XrefRowId, extend: SelectExtend) { + let Some(index) = self.row_index(id) else { + return; + }; if index == HOST_ROW || index >= self.entries.len() { return; } @@ -365,19 +395,29 @@ impl XrefManagerPanel { /// Legacy toggle entry point: Ctrl-style toggle in list mode, single /// select in tree mode. Kept for tests and non-pointer callers. pub fn toggle_select(&mut self, index: usize) { - self.click_select(index, SelectExtend::Toggle); + let Some(id) = self + .entries + .get(index) + .map(|e| XrefRowId::of(e.key, &e.saved_path)) + else { + return; + }; + self.click_select(&id, SelectExtend::Toggle); } /// Right-click selection: select the row alone when it is not already /// selected (standard right-click behavior — the menu then acts on the /// selection, which now includes this row). Keeps multi-selections when /// right-clicking inside them. - pub fn right_click_select(&mut self, index: usize) { + pub fn right_click_select(&mut self, id: &XrefRowId) { + let Some(index) = self.row_index(id) else { + return; + }; if index == HOST_ROW || index >= self.entries.len() { return; } if !self.selected.contains(&index) { - self.click_select(index, SelectExtend::Single); + self.click_select(id, SelectExtend::Single); } else { self.ensure_preview_for_anchor(); } @@ -1627,14 +1667,14 @@ fn menu_separator() -> Element<'static, Message> { /// path tools. No Bind, no Attach/Overlay — those are DWG-only and /// hidden entirely. Change Path Type shows a right triangle and a /// second list adjacent to "Change Path Type" on hover. -fn row_menu_for(index: usize, change_path_open: bool) -> Element<'static, Message> { +fn row_menu_for(id: XrefRowId, change_path_open: bool) -> Element<'static, Message> { let item = |label: &str, op: XrefPaletteOp| -> Element<'static, Message> { - menu_row(label.to_string(), Message::XrefRowOp(index, op)) + menu_row(label.to_string(), Message::XrefRowOp(id.clone(), op)) }; let pathtype_item = |label: &str, pt: Pathtype| -> Element<'static, Message> { submenu_row( label.to_string(), - Message::XrefRowOp(index, XrefPaletteOp::Pathtype(pt)), + Message::XrefRowOp(id.clone(), XrefPaletteOp::Pathtype(pt)), ) }; let change_path_row: Element<'static, Message> = mouse_area( @@ -1681,11 +1721,11 @@ fn row_menu_for(index: usize, change_path_open: bool) -> Element<'static, Messag menu_separator(), menu_row( crate::t!("Select New Path").into_owned(), - Message::XrefRowPathPick(index), + Message::XrefRowPathPick(id.clone()), ), menu_row( crate::t!("Find and Replace...").into_owned(), - Message::XrefRowFindReplacePrompt(index), + Message::XrefRowFindReplacePrompt(id.clone()), ), ] .spacing(MENU_SPACING) @@ -1821,6 +1861,8 @@ fn xref_row<'a>( .align_y(iced::Center); let index = display.index; + // #49: messages carry stable identity; the index stays for zebra striping only. + let row_id = XrefRowId::of(entry.key, &entry.saved_path); let row = mouse_area( container(content) .style(move |theme: &Theme| { @@ -1847,9 +1889,12 @@ fn xref_row<'a>( .height(Length::Fixed(ROW_H)) .width(Fill), ) - .on_press(Message::XrefManagerSelect(index)); - let cm = iced_aw::ContextMenu::new(row, move || row_menu_for(index, change_path_open)); - RightClickArea::new(cm, Message::XrefRowRightClick(index)).into() + .on_press(Message::XrefManagerSelect(row_id.clone())); + let cm = iced_aw::ContextMenu::new(row, { + let row_id = row_id.clone(); + move || row_menu_for(row_id.clone(), change_path_open) + }); + RightClickArea::new(cm, Message::XrefRowRightClick(row_id)).into() } /// One tree-mode row: indent + optional expand arrow + file icon + name. @@ -1883,6 +1928,8 @@ fn tree_row( ); cells = cells.push(text(entry.name.clone()).size(TREE_FONT_SZ)); let index = display.index; + // #49: messages carry stable identity; the index stays for zebra striping only. + let row_id = XrefRowId::of(entry.key, &entry.saved_path); let row = mouse_area( container(cells.spacing(4).width(Fill)) .style(move |theme: &Theme| { @@ -1908,9 +1955,12 @@ fn tree_row( }) .width(Fill), ) - .on_press(Message::XrefManagerSelect(index)); - let cm = iced_aw::ContextMenu::new(row, move || row_menu_for(index, change_path_open)); - RightClickArea::new(cm, Message::XrefRowRightClick(index)).into() + .on_press(Message::XrefManagerSelect(row_id.clone())); + let cm = iced_aw::ContextMenu::new(row, { + let row_id = row_id.clone(); + move || row_menu_for(row_id.clone(), change_path_open) + }); + RightClickArea::new(cm, Message::XrefRowRightClick(row_id)).into() } /// Tree-mode host root: home icon + name with the current-drawing marker. @@ -2344,6 +2394,8 @@ mod tests { #[test] fn click_select_single_then_range() { use super::SelectExtend; + use super::XrefRowId; + let id = |key: u64, saved: &str| XrefRowId::of(key, saved); let mut panel = XrefManagerPanel::default(); panel.entries = vec![ entry(1, "A", "a.dwg"), @@ -2351,27 +2403,27 @@ mod tests { entry(3, "C", "c.dwg"), ]; // Plain click selects exactly one row. - panel.click_select(0, SelectExtend::Single); - panel.click_select(2, SelectExtend::Single); + panel.click_select(&id(1, "a.dwg"), SelectExtend::Single); + panel.click_select(&id(3, "c.dwg"), SelectExtend::Single); assert_eq!(panel.selected, HashSet::from([2])); assert_eq!(panel.anchor, Some(2)); // Shift-click extends a contiguous range from the anchor. - panel.click_select(2, SelectExtend::Single); - panel.click_select(0, SelectExtend::Range); + panel.click_select(&id(3, "c.dwg"), SelectExtend::Single); + panel.click_select(&id(1, "a.dwg"), SelectExtend::Range); assert_eq!(panel.selected, HashSet::from([0, 1, 2])); // Repeated extends keep the original anchor — rows are never lost. panel.entries.push(entry(4, "D", "d.dwg")); panel.entries.push(entry(5, "E", "e.dwg")); - panel.click_select(4, SelectExtend::Range); + panel.click_select(&id(5, "e.dwg"), SelectExtend::Range); assert_eq!(panel.selected, HashSet::from([2, 3, 4])); assert_eq!(panel.anchor, Some(2)); // Ctrl-click toggles one row. - panel.click_select(3, SelectExtend::Toggle); + panel.click_select(&id(4, "d.dwg"), SelectExtend::Toggle); assert_eq!(panel.selected, HashSet::from([2, 4])); // Range with no anchor starts at the clicked row. let mut fresh = XrefManagerPanel::default(); fresh.entries = vec![entry(1, "A", "a.dwg"), entry(2, "B", "b.dwg")]; - fresh.click_select(1, SelectExtend::Range); + fresh.click_select(&id(2, "b.dwg"), SelectExtend::Range); assert_eq!(fresh.selected, HashSet::from([1])); } @@ -2570,9 +2622,10 @@ mod tests { /// opens it with a synthetic right-click on the underlay, mirroring a /// user right-clicking a reference row. fn open_row_menu(change_path_open: bool) -> MenuSimulator { + let row_id = XrefRowId::of(0, ""); let mut ui = iced_test::simulator(iced_aw::ContextMenu::new( text("anchor"), - move || row_menu_for(0, change_path_open), + move || row_menu_for(row_id.clone(), change_path_open), )); let anchor = ui.find("anchor").expect("anchor underlay present"); let center = anchor @@ -2586,11 +2639,11 @@ mod tests { ui } - fn published_ops(ui: MenuSimulator) -> Vec<(usize, XrefPaletteOp)> { + fn published_ops(ui: MenuSimulator) -> Vec<(XrefRowId, XrefPaletteOp)> { let mut ops = Vec::new(); for message in ui.into_messages() { - if let Message::XrefRowOp(index, op) = message { - ops.push((index, op)); + if let Message::XrefRowOp(id, op) = message { + ops.push((id, op)); } } ops @@ -2603,8 +2656,8 @@ mod tests { ui.click(unload.as_str()) .expect("Unload row is clickable inside the overlay"); assert!( - published_ops(ui).contains(&(0, XrefPaletteOp::Unload)), - "clicking Unload must publish XrefRowOp(0, Unload)" + published_ops(ui).contains(&(XrefRowId::of(0, ""), XrefPaletteOp::Unload)), + "clicking Unload must publish XrefRowOp(row, Unload)" ); } @@ -2643,7 +2696,7 @@ mod tests { ui.click(make_absolute.as_str()) .expect("flyout row is clickable inside the overlay"); assert!( - published_ops(ui).contains(&(0, XrefPaletteOp::Pathtype(Pathtype::Full))), + published_ops(ui).contains(&(XrefRowId::of(0, ""), XrefPaletteOp::Pathtype(Pathtype::Full))), "clicking Make Absolute must publish the Full-pathtype op" ); } @@ -2671,7 +2724,7 @@ mod tests { // buttons again. use iced_core::Widget; - let mut menu = iced_aw::ContextMenu::new(text("anchor"), || row_menu_for(0, false)); + let mut menu = iced_aw::ContextMenu::new(text("anchor"), || row_menu_for(XrefRowId::of(0, ""), false)); let mut tree = iced_core::widget::Tree::new( &menu as &dyn Widget, );