refactor: break Model↔View cycle, introduce Workbook wrapper
Model is now pure data (categories, cells, formulas, measure_agg) with no references to view/. The Workbook struct owns the Model together with views and the active view name, and is responsible for cross-slice operations (add/remove category → notify views, view management). - New: src/workbook.rs with Workbook wrapper and cross-slice helpers (add_category, add_label_category, remove_category, create_view, switch_view, delete_view, normalize_view_state). - Model: strip view state and view-touching methods. recompute_formulas remains on Model as a primitive; the view-derived none_cats list is gathered at each call site (App::rebuild_layout, persistence::load) so the view dependency is explicit, not hidden behind a wrapper. - View: add View::none_cats() helper. - CmdContext: add workbook and view fields so commands can reach both slices without threading Model + View through every call. - App: rename `model` field to `workbook`. - Persistence (save/load/format_md/parse_md/export_csv): take/return Workbook so the on-disk format carries model + views together. - Widgets (GridWidget, TileBar, CategoryContent, ViewContent): take explicit &Model + &View instead of routing through Model. Tests updated throughout to reflect the new shape. View-management tests that previously lived on Model continue to cover the same behaviour via a build_workbook() helper in model/types.rs. All 573 tests pass; clippy is clean. This is Phase A of improvise-36h. Phase B will mechanically extract crates/improvise-core/ containing model/, view/, format.rs, workbook.rs. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@ -21,10 +21,10 @@ pub(super) mod test_helpers {
|
||||
|
||||
use crossterm::event::KeyCode;
|
||||
|
||||
use crate::model::Model;
|
||||
use crate::ui::app::AppMode;
|
||||
use crate::ui::effect::Effect;
|
||||
use crate::view::GridLayout;
|
||||
use crate::workbook::Workbook;
|
||||
|
||||
use super::core::CmdContext;
|
||||
use super::registry::default_registry;
|
||||
@ -36,19 +36,21 @@ pub(super) mod test_helpers {
|
||||
pub static EMPTY_EXPANDED: std::sync::LazyLock<std::collections::HashSet<String>> =
|
||||
std::sync::LazyLock::new(std::collections::HashSet::new);
|
||||
|
||||
pub fn make_layout(model: &Model) -> GridLayout {
|
||||
GridLayout::new(model, model.active_view())
|
||||
pub fn make_layout(workbook: &Workbook) -> GridLayout {
|
||||
GridLayout::new(&workbook.model, workbook.active_view())
|
||||
}
|
||||
|
||||
pub fn make_ctx<'a>(
|
||||
model: &'a Model,
|
||||
workbook: &'a Workbook,
|
||||
layout: &'a GridLayout,
|
||||
registry: &'a CmdRegistry,
|
||||
) -> CmdContext<'a> {
|
||||
let view = model.active_view();
|
||||
let view = workbook.active_view();
|
||||
let (sr, sc) = view.selected;
|
||||
CmdContext {
|
||||
model,
|
||||
model: &workbook.model,
|
||||
workbook,
|
||||
view,
|
||||
layout,
|
||||
registry,
|
||||
mode: &AppMode::Normal,
|
||||
@ -72,7 +74,7 @@ pub(super) mod test_helpers {
|
||||
display_value: {
|
||||
let key = layout.cell_key(sr, sc);
|
||||
key.as_ref()
|
||||
.and_then(|k| model.get_cell(k).cloned())
|
||||
.and_then(|k| workbook.model.get_cell(k).cloned())
|
||||
.map(|v| v.to_string())
|
||||
.unwrap_or_default()
|
||||
},
|
||||
@ -83,32 +85,32 @@ pub(super) mod test_helpers {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn two_cat_model() -> Model {
|
||||
let mut m = Model::new("Test");
|
||||
m.add_category("Type").unwrap();
|
||||
m.add_category("Month").unwrap();
|
||||
m.category_mut("Type").unwrap().add_item("Food");
|
||||
m.category_mut("Type").unwrap().add_item("Clothing");
|
||||
m.category_mut("Month").unwrap().add_item("Jan");
|
||||
m.category_mut("Month").unwrap().add_item("Feb");
|
||||
m
|
||||
pub fn two_cat_model() -> Workbook {
|
||||
let mut wb = Workbook::new("Test");
|
||||
wb.add_category("Type").unwrap();
|
||||
wb.add_category("Month").unwrap();
|
||||
wb.model.category_mut("Type").unwrap().add_item("Food");
|
||||
wb.model.category_mut("Type").unwrap().add_item("Clothing");
|
||||
wb.model.category_mut("Month").unwrap().add_item("Jan");
|
||||
wb.model.category_mut("Month").unwrap().add_item("Feb");
|
||||
wb
|
||||
}
|
||||
|
||||
pub fn three_cat_model_with_page() -> Model {
|
||||
let mut m = Model::new("Test");
|
||||
m.add_category("Type").unwrap();
|
||||
m.add_category("Month").unwrap();
|
||||
m.add_category("Region").unwrap();
|
||||
m.category_mut("Type").unwrap().add_item("Food");
|
||||
m.category_mut("Type").unwrap().add_item("Clothing");
|
||||
m.category_mut("Month").unwrap().add_item("Jan");
|
||||
m.category_mut("Month").unwrap().add_item("Feb");
|
||||
m.category_mut("Region").unwrap().add_item("North");
|
||||
m.category_mut("Region").unwrap().add_item("South");
|
||||
m.category_mut("Region").unwrap().add_item("East");
|
||||
let view = m.active_view_mut();
|
||||
view.set_axis("Region", crate::view::Axis::Page);
|
||||
m
|
||||
pub fn three_cat_model_with_page() -> Workbook {
|
||||
let mut wb = Workbook::new("Test");
|
||||
wb.add_category("Type").unwrap();
|
||||
wb.add_category("Month").unwrap();
|
||||
wb.add_category("Region").unwrap();
|
||||
wb.model.category_mut("Type").unwrap().add_item("Food");
|
||||
wb.model.category_mut("Type").unwrap().add_item("Clothing");
|
||||
wb.model.category_mut("Month").unwrap().add_item("Jan");
|
||||
wb.model.category_mut("Month").unwrap().add_item("Feb");
|
||||
wb.model.category_mut("Region").unwrap().add_item("North");
|
||||
wb.model.category_mut("Region").unwrap().add_item("South");
|
||||
wb.model.category_mut("Region").unwrap().add_item("East");
|
||||
wb.active_view_mut()
|
||||
.set_axis("Region", crate::view::Axis::Page);
|
||||
wb
|
||||
}
|
||||
|
||||
pub fn effects_debug(effects: &[Box<dyn Effect>]) -> String {
|
||||
|
||||
Reference in New Issue
Block a user