Commit Graph

6 Commits

Author SHA1 Message Date
a2e519efcc refactor: replace CellValue::Empty with Option<CellValue>
Previously CellValue had three variants: Number, Text, and Empty.
The Empty variant acted as a null sentinel, but the compiler could not
distinguish between "this is a real value" and "this might be empty".
Code that received a CellValue could use it without checking for Empty,
because there was no type-level enforcement.

Now CellValue has only Number and Text. The absence of a value is
represented as None at every API boundary:

  DataStore::get()    → Option<&CellValue>  (was &CellValue / Empty)
  Model::get_cell()   → Option<&CellValue>  (was &CellValue / Empty)
  Model::evaluate()   → Option<CellValue>   (was CellValue::Empty)
  eval_formula()      → Option<CellValue>   (was CellValue::Empty)

Model gains clear_cell() for explicit key removal; ClearCell dispatch
calls it instead of set_cell(key, CellValue::Empty).

The compiler now forces every caller of evaluate/get_cell to handle
the None case explicitly — accidental use of an empty value as if it
were real is caught at compile time rather than silently computing
wrong results.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-24 08:06:51 -07:00
e680b098ec refactor: remove Axis::Unassigned; axis_of returns Option<Axis>
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>
2026-03-24 07:32:48 -07:00
d99d22820e refactor: extract GridLayout as single source for view→grid mapping
Three copies of cross_product existed (grid.rs, app.rs, persistence/mod.rs)
with slightly different signatures. Extracted into GridLayout in
src/view/layout.rs, which is now the single canonical mapping from a View
to a 2-D grid: row/col counts, labels, and cell_key(row, col) → CellKey.

All consumers updated to use GridLayout::new(model, view):
- grid.rs: render_grid, total-row computation, page bar
- persistence/mod.rs: export_csv
- app.rs: move_selection, jump_to_last_row/col, scroll_rows,
  search_navigate, selected_cell_key

Also includes two app.rs UI bug fixes that were discovered while
refactoring:
- Ctrl+Arrow tile movement was unreachable (shadowed by plain arrow arms);
  moved before plain arrow handlers
- RemoveFormula dispatch now passes target_category (required by the
  formula management fix in the previous commit)

GridLayout has 6 unit tests covering counts, label formatting, cell_key
correctness, out-of-bounds, page coord inclusion, and evaluate round-trip.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-24 00:12:00 -07:00
09caf815d3 test: add proptest property-based tests
Add proptest dependency and property tests for:
- CellKey: key normalization invariants (sort order, dedup, round-trip,
  prefix non-equality, merge commutativity)
- View: axis exclusivity, set_axis, idempotency, page_selection roundtrip,
  hide/show roundtrip, toggle_group_collapse involution

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-24 00:10:44 -07:00
56d11aee74 Test: add unit tests co-located with the code they cover
- model/cell.rs: CellKey (sorting, get, with, without, matches_partial,
  display) and DataStore (set/get, overwrite, empty-removes-key,
  sum_matching, matching_cells, text exclusion)
- model/category.rs: item ids, deduplication, group assignment,
  top_level_groups, item_index insertion order
- formula/parser.rs: subtraction, WHERE clause, SUM/AVG, IF,
  numeric literal, chained arithmetic, error on missing =
- view/view.rs: auto-axis assignment, set_axis, categories_on,
  page_selection, group collapse toggle, hide/show, cycle_axis
  (all transitions + scroll/selection reset)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 23:28:48 -07:00
eae00522e2 Initial implementation of Improvise TUI
Multi-dimensional data modeling terminal application with:
- Core data model: categories, items, groups, sparse cell store
- Formula system: recursive-descent parser, named formulas, WHERE clauses
- View system: Row/Column/Page axes, tile-based pivot, page slicing
- JSON import wizard (interactive TUI + headless auto-mode)
- Command layer: all mutations via typed Command enum for headless replay
- TUI: Ratatui grid, tile bar, formula/category/view panels, help overlay
- Persistence: .improv (JSON), .improv.gz (gzip), CSV export, autosave
- Static binary via x86_64-unknown-linux-musl + nix flake devShell
- Headless mode: --cmd '{"op":"..."}' and --script file.jsonl

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 21:11:55 -07:00