From 2824b2943f05822e7ec51891d3c4bdcf0b5e98fc Mon Sep 17 00:00:00 2001 From: Karim Jerbi Date: Sun, 11 Oct 2026 02:14:48 +0100 Subject: [PATCH] fix(styles): reload Table/MLine buffers on structural ops, validate cell text style style_new/copy/delete funneled through a load_style_bufs no-op for Table/MLine, leaving stale editor buffers pointed at the fresh style; both arms now route to the existing loaders. Table cell text-style typos persisted as dangling names; unknown names are now rejected with command-line feedback, keeping the buffer text for correction. Silent numeric parse-drops deliberately untouched (codebase-wide keep-old-valid norm). Tests: table_new_reloads_cell_buffers plus table_cell_unknown_text_style_is_rejected incl. retention assert (RED each verified by temporary revert). Full suite: 2048 passed / 0 failed / 25 ignored, zero warnings. --- src/app/style_ops.rs | 3 +- src/app/update/command.rs | 62 +++++++++++++++++++++++++++++++++++++++ src/app/update/style.rs | 17 +++++++++-- 3 files changed, 79 insertions(+), 3 deletions(-) diff --git a/src/app/style_ops.rs b/src/app/style_ops.rs index 64ea03ce8..3a8bb12cd 100644 --- a/src/app/style_ops.rs +++ b/src/app/style_ops.rs @@ -309,7 +309,8 @@ impl OpenCADStudio { StyleKind::Text => self.load_textstyle_bufs(i), StyleKind::Dim => self.load_dimstyle_bufs(i), StyleKind::MLeader => self.load_mleaderstyle_bufs(i), - StyleKind::Table | StyleKind::MLine => {} + StyleKind::Table => self.load_tablestyle_bufs(i), + StyleKind::MLine => self.load_mlstyle_bufs(i), } } diff --git a/src/app/update/command.rs b/src/app/update/command.rs index a54f841f1..a14fdfcf7 100644 --- a/src/app/update/command.rs +++ b/src/app/update/command.rs @@ -4924,6 +4924,68 @@ mod tablestyle_ribbon_tests { } } +#[cfg(test)] +mod tablestyle_buffer_tests { + // #53: New/Copy/Delete must reload Table/MLine buffers (no stale edits + // into the fresh style), and cell text-style typos must not persist. + use crate::app::{OpenCADStudio, StyleKind}; + + #[test] + fn table_new_reloads_cell_buffers() { + let mut app = OpenCADStudio::new_for_test(); + app.automation_op(r#"{"op":"new"}"#); + app.ts_cell_textstyle[0] = "STALE_SENTINEL".to_string(); + app.style_new(StyleKind::Table); + assert_ne!( + app.ts_cell_textstyle[0], "STALE_SENTINEL", + "buffers must reload from the fresh style" + ); + } + + #[test] + fn table_cell_unknown_text_style_is_rejected() { + let mut app = OpenCADStudio::new_for_test(); + app.automation_op(r#"{"op":"new"}"#); + let i = app.active_tab; + let style_name = app + .tabs[i] + .scene + .document + .objects + .values() + .find_map(|o| match o { + codec::objects::ObjectType::TableStyle(s) => Some(s.name.clone()), + _ => None, + }) + .expect("fresh doc ships a table style"); + app.tablestyle_selected = style_name.clone(); + app.ts_cell_textstyle[0] = "NOPE".to_string(); + let before = app.command_line.history.len(); + let _ = app.on_table_style_cell_apply(0); + let cell_name = app.tabs[i] + .scene + .document + .objects + .values() + .find_map(|o| match o { + codec::objects::ObjectType::TableStyle(s) if s.name == style_name => { + Some(s.data_row_style.text_style_name.clone()) + } + _ => None, + }) + .unwrap(); + assert_ne!(cell_name, "NOPE", "dangling name must not persist"); + assert_eq!( + app.ts_cell_textstyle[0], "NOPE", + "rejected text stays in the buffer for correction" + ); + assert!( + app.command_line.history.len() > before, + "rejection must say something" + ); + } +} + #[cfg(test)] mod plot_style_ctb_guard_tests { use crate::app::OpenCADStudio; diff --git a/src/app/update/style.rs b/src/app/update/style.rs index 5614d7e0f..664f4d370 100644 --- a/src/app/update/style.rs +++ b/src/app/update/style.rs @@ -692,12 +692,20 @@ pub(super) fn on_text_style_dialog_open(&mut self) -> Task { self.ts_border_spacing[r][b].trim().parse().ok(), ) }); + // Reject dangling references: a typo must not persist as a text + // style that does not exist (#53). Decided up front so the + // cell borrow below does not conflict with command-line I/O. + let ts_known = ts.is_empty() + || self.tabs[i].scene.document.text_styles.iter().any(|s| { + s.name.eq_ignore_ascii_case(&ts) + }); + let ts_rejected = !ts.is_empty() && !ts_known; if let Some(c) = self .tablestyle_mut(i) .and_then(|s| Self::ts_cell_of(s, row)) { - if !ts.is_empty() { - c.text_style_name = ts; + if !ts.is_empty() && ts_known { + c.text_style_name = ts.clone(); } if let Some(h) = height { c.text_height = h; @@ -729,6 +737,11 @@ pub(super) fn on_text_style_dialog_open(&mut self) -> Task { } } } + if ts_rejected { + self.command_line.push_error( + crate::tf!("Unknown text style '{ts}'.").as_ref(), + ); + } Task::none() }