From 5434a60cc44564374f8e20b0ff86ff1d3b5be78f Mon Sep 17 00:00:00 2001 From: Ed L Date: Tue, 24 Mar 2026 00:18:43 -0700 Subject: [PATCH] refactor: make Model::formulas private, expose read-only accessor Previously `pub formulas: Vec` allowed any code to call `model.formulas.push(formula)` directly, bypassing the dedup logic in `add_formula` that enforces the (target, target_category) uniqueness invariant. Making the field private means the only mutation paths are `add_formula` and `remove_formula`, both of which maintain the invariant. A `pub fn formulas(&self) -> &[Formula]` accessor preserves read access for the UI and tests. Co-Authored-By: Claude Sonnet 4.6 --- src/model/model.rs | 6 +++++- src/ui/app.rs | 8 ++++---- src/ui/formula_panel.rs | 2 +- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/model/model.rs b/src/model/model.rs index 519b051..58ffa79 100644 --- a/src/model/model.rs +++ b/src/model/model.rs @@ -14,7 +14,7 @@ pub struct Model { pub name: String, pub categories: IndexMap, pub data: DataStore, - pub formulas: Vec, + formulas: Vec, pub views: IndexMap, pub active_view: String, next_category_id: CategoryId, @@ -86,6 +86,10 @@ impl Model { self.formulas.retain(|f| !(f.target == target && f.target_category == target_category)); } + pub fn formulas(&self) -> &[Formula] { + &self.formulas + } + pub fn active_view(&self) -> Option<&View> { self.views.get(&self.active_view) } diff --git a/src/ui/app.rs b/src/ui/app.rs index 43171f7..9f8d55c 100644 --- a/src/ui/app.rs +++ b/src/ui/app.rs @@ -595,7 +595,7 @@ impl App { fn handle_formula_panel_key(&mut self, key: KeyEvent) -> Result<()> { // Clamp cursor in case the formula list shrank since it was last set. - let flen = self.model.formulas.len(); + let flen = self.model.formulas().len(); if flen == 0 { self.formula_cursor = 0; } else { self.formula_cursor = self.formula_cursor.min(flen - 1); } @@ -605,8 +605,8 @@ impl App { self.mode = AppMode::FormulaEdit { buffer: String::new() }; } KeyCode::Char('d') | KeyCode::Delete => { - if self.formula_cursor < self.model.formulas.len() { - let f = &self.model.formulas[self.formula_cursor]; + if self.formula_cursor < self.model.formulas().len() { + let f = &self.model.formulas()[self.formula_cursor]; let target = f.target.clone(); let target_category = f.target_category.clone(); command::dispatch(&mut self.model, &Command::RemoveFormula { target, target_category }); @@ -618,7 +618,7 @@ impl App { if self.formula_cursor > 0 { self.formula_cursor -= 1; } } KeyCode::Down | KeyCode::Char('j') => { - if self.formula_cursor + 1 < self.model.formulas.len() { self.formula_cursor += 1; } + if self.formula_cursor + 1 < self.model.formulas().len() { self.formula_cursor += 1; } } _ => {} } diff --git a/src/ui/formula_panel.rs b/src/ui/formula_panel.rs index 5e92452..495f5e5 100644 --- a/src/ui/formula_panel.rs +++ b/src/ui/formula_panel.rs @@ -36,7 +36,7 @@ impl<'a> Widget for FormulaPanel<'a> { let inner = block.inner(area); block.render(area, buf); - let formulas = &self.model.formulas; + let formulas = self.model.formulas(); if formulas.is_empty() { buf.set_string(inner.x, inner.y,