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>
Refactor AppMode to use MinibufferConfig for all text-entry modes. Update
command implementations to use new mode constructors. Introduce
PanelContent trait and replace panel structs with content types. Adjust
rendering to use Panel::new and minibuffer configuration. Update imports
and add MinibufferConfig struct. No functional changes; all behavior
preserved.
Co-Authored-By: fiddlerwoaroof/git-smart-commit (bartowski/nvidia_Nemotron-Cascade-2-30B-A3B-GGUF)
Clean up UI panel titles by removing redundant keyboard hints that are
already present in the app's status bar or are no longer needed.
Co-Authored-By: fiddlerwoaroof/git-smart-commit (unsloth/gemma-4-31B-it-GGUF:UD-Q5_K_XL)
Add a tree-based category panel that supports expand/collapse of categories.
Introduces CatTreeEntry and build_cat_tree to render categories as
a collapsible tree. The category panel now displays categories with
expand indicators (▶/▼) and shows items under expanded categories.
CmdContext gains cat_tree_entry(), cat_at_cursor(), and cat_tree_len()
methods to work with the tree. App tracks expanded_cats in a HashSet.
Keymap updates: Enter in category panel now triggers filter-to-item.
Co-Authored-By: fiddlerwoaroof/git-smart-commit (unsloth/Qwen3.5-35B-A3B-GGUF:Q5_K_M)
Categories on the None axis are excluded from the grid and cell keys.
When evaluating cells, values across hidden dimensions are aggregated
using a per-measure function (default SUM). Adds evaluate_aggregated
to Model, none_cats to GridLayout, and 'n' shortcut in TileSelect.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Both functions previously returned Option despite their invariants
guaranteeing a value: active_view always names an existing view
(maintained by new/switch_view/delete_view), and axis_of only returns
None for categories never registered with the view (a programming error).
Callers no longer need to handle the impossible None case, eliminating
~15 match/if-let Option guards across app.rs, dispatch.rs, grid.rs,
tile_bar.rs, and category_panel.rs.
Also adds Model::evaluate_f64 (returns 0.0 for empty cells) and collapses
the double match-on-axis pattern in tile_bar/category_panel into a single
axis_display(Axis) helper.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Axis::Unassigned served two purposes that Option already covers:
1. "this category has no assignment yet" → None
2. "this category doesn't exist" → None
By removing the variant and changing axis_of to return Option<Axis>,
callers are forced by the compiler to handle the absent-category case
explicitly (via match or unwrap_or), rather than silently treating it
like a real axis value.
SetAxis { axis: String } also upgraded to SetAxis { axis: Axis }.
Previously, constructing SetAxis with an invalid string (e.g. "diagonal")
would compile and then silently fail at dispatch. Now the type only admits
valid axis values; the dispatch string-parser is gone.
Axis gains #[serde(rename_all = "lowercase")] so existing JSON command
files (smoke.jsonl, etc.) using "row"/"column"/"page" continue to work.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
N (from anywhere) or n (in Category panel) opens an inline prompt
to add categories one after another without typing :add-cat each time.
- Yellow border + prompt distinguishes it from item-add (green)
- Enter / Tab adds the category and clears the buffer, staying open
- Esc returns to the category list
- Cursor automatically moves to the newly added category
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two new ways to add multiple items without repeating yourself:
1. :add-items <category> item1 item2 item3 ...
Adds all space-separated items in one command.
2. Category panel quick-add mode (press 'a' or 'o' on a category):
- Opens an inline prompt at the bottom of the panel
- Enter adds the item and clears the buffer — stays open for next entry
- Tab does the same as Enter
- Esc closes and returns to the category list
- The panel border turns green and the title updates to signal add mode
- Item count in the category list updates live as items are added
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>