Commit Graph

18 Commits

Author SHA1 Message Date
c8b9d29690 feat(model): introduce virtual '_Measure' category for formulas
Refactor formula handling to use a virtual '_Measure' category.

- Formulas now default to targeting '_Measure' if no category is specified.
- '_Measure' items are dynamically computed from existing data items and
  formula targets, preventing redundant item storage.
- Updated persistence to handle '_Measure' formulas and items correctly in
  Markdown.
- Updated UI and model to use 'effective_item_names' for layout and item
  resolution.
- Updated tests to reflect the new '_Measure' behavior.

Co-Authored-By: fiddlerwoaroof/git-smart-commit (unsloth/gemma-4-26B-A4B-it-GGUF:UD-Q5_K_XL)
2026-04-11 00:08:01 -07:00
23a876d556 feat: rename Measure to _Measure (virtual), add fixed-point formula eval
_Measure is now a virtual category (CategoryKind::VirtualMeasure),
created automatically in Model::new() alongside _Index and _Dim.
Formula targets are added as items automatically by add_formula.

Formula evaluation uses a fixed-point cache: recompute_formulas()
iterates evaluation of all formula cells until values stabilize,
resolving refs through the cache for formula values and raw data
aggregation for non-formula values. This fixes formulas that
reference other measures when hidden dimensions are present.

evaluate_aggregated now checks the formula cache instead of
recursively evaluating formulas, breaking the dependency between
formula evaluation and aggregation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 00:07:59 -07:00
6f3af34056 chore: clippy 2026-04-11 00:06:48 -07:00
9b8f939245 misc 2026-04-11 00:06:48 -07:00
56838c0a61 style: cleanup formatting and code style across the project
Clean up formatting and code style across the project.

- Remove unnecessary whitespace and empty lines in `src/model/cell.rs` ,
  `src/model/types.rs` , and `src/draw.rs` .
- Reformat long lines and function calls in `src/command/cmd.rs` ,
  `src/ui/grid.rs` , and `src/ui/tile_bar.rs` for better readability.
- Consolidate imports and simplify expressions in `src/command/keymap.rs` and
  `src/ui/app.rs` .

Co-Authored-By: fiddlerwoaroof/git-smart-commit (unsloth/gemma-4-26B-A4B-it-GGUF:UD-Q5_K_XL)
2026-04-11 00:06:48 -07:00
812760233b refactor(view): use Rc for sharing records in GridLayout
Refactor GridLayout to use Rc for sharing records and simplify its structure.
This improves performance when cloning the layout and ensures consistent data
access.

- Use Rc<Vec<(CellKey, CellValue)>> for records in GridLayout
- Update with_frozen_records and other methods to handle Rc
- Simplify layout construction and sorting logic

Co-Authored-By: fiddlerwoaroof/git-smart-commit (unsloth/gemma-4-26B-A4B-it-GGUF:UD-Q5_K_XL)
2026-04-11 00:06:48 -07:00
b7e5115a8e refactor(view): unify cell handling for records and pivot modes
Unifies cell text retrieval and formatting across pivot and records modes.
Introduces `GridLayout::display_text` to centralize how cell content is
resolved, reducing duplication in `GridWidget` and `export_csv`.
Moves formatting logic from `src/ui/grid.rs` to a new dedicated `src/format.rs`
module to improve reusability.

Co-Authored-By: fiddlerwoaroof/git-smart-commit (unsloth/gemma-4-31B-it-GGUF:UD-Q5_K_XL)
2026-04-11 00:06:48 -07:00
fbd672d5ed refactor!: unify records and pivot mode cell handling
Refactor records mode to use synthetic CellKeys (_Index, _Dim) for all columns,
allowing uniform handling of display values and edits across both pivot and
records modes.

- Introduce `synthetic_record_info` to extract metadata from synthetic keys.
- Update `GridLayout::cell_key` to return synthetic keys in records mode.
- Add `GridLayout::resolve_display` to handle value resolution for synthetic
  keys.
- Replace `records_col` and `records_value` in `CmdContext` with a unified
  `display_value`.
- Update `EditOrDrill` and `AddRecordRow` to use synthetic key detection.
- Refactor `CommitCellEdit` to use a shared `commit_cell_value` helper.

BREAKING CHANGE: CmdContext fields `records_col` and `records_value` are replaced by
`display_value` .
Co-Authored-By: fiddlerwoaroof/git-smart-commit (unsloth/gemma-4-31B-it-GGUF:UD-Q5_K_XL)
2026-04-11 00:06:48 -07:00
eb83df9984 feat: add prune empty feature for pivot views
Add prune_empty feature to hide empty rows/columns in pivot mode.

View gains prune_empty boolean (default false for backward compat).
GridLayout::prune_empty() removes data rows where all columns are
empty and data columns where all rows are empty.

Group headers are preserved if at least one data item survives.
In records mode, pruning is skipped (user drilled in to see all data).

EditOrDrill command updated to check for regular (non-virtual)
categories when determining if a cell is aggregated.

Co-Authored-By: fiddlerwoaroof/git-smart-commit (unsloth/Qwen3.5-35B-A3B-GGUF:Q5_K_M)
2026-04-11 00:06:48 -07:00
78df3a4949 feat: add records-mode drill-down with staged edits
Introduce records-mode drill-down functionality that allows users to
edit individual records without immediately modifying the underlying model.

Key changes:
- Added DrillState struct to hold frozen records snapshot and pending edits
- New effects: StartDrill, ApplyAndClearDrill, SetDrillPendingEdit
- Extended CmdContext with records_col and records_value for records mode
- CommitCellEdit now stages edits in pending_edits when in records mode
- DrillIntoCell captures a snapshot before switching to drill view
- GridLayout supports frozen records for stable view during edits
- GridWidget renders with drill_state for pending edit display

In records mode, edits are staged and only applied to the model when
the user navigates away or commits. This prevents data loss and allows
batch editing of records.

Co-Authored-By: fiddlerwoaroof/git-smart-commit (unsloth/Qwen3.5-35B-A3B-GGUF:Q5_K_M)
2026-04-05 11:45:36 -07:00
19645a34cf feat: add records mode (long-format view) for drill-down
Implement records mode (long-format view) when drilling into aggregated cells.

Key changes:
- DrillIntoCell now creates views with _Index on Row and _Dim on Column
- GridLayout detects records mode and builds a records list instead of
  cross-product row/col items
- Added records_display() to render individual cell values in records mode
- GridWidget and CSV export updated to handle records mode rendering
- category_names() now includes virtual categories (_Index, _Dim)
- Tests updated to reflect virtual categories counting toward limits

Co-Authored-By: fiddlerwoaroof/git-smart-commit (unsloth/Qwen3.5-35B-A3B-GGUF:Q5_K_M)
2026-04-05 11:10:41 -07:00
5a251a1cbe feat: add Axis::None for hidden dimensions with implicit aggregation
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>
2026-04-02 16:38:35 -07:00
edd6431444 refactor: use data_col_to_visual via group_for helpers, add column group toggle
Add row_group_for/col_group_for to GridLayout, replacing inline
backward-search logic. Refactor grid renderer to use col_group_for
instead of pre-filtering col_items. Add gz keybinding for column
group collapse toggle, symmetric with z for rows.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 10:22:00 -07:00
fe74cc5fcb chore: clippy + fmt 2026-04-02 09:35:02 -07:00
37584670eb feat: group-aware grid rendering and hide/show item
Builds out two half-finished view features:

Group collapse:
- AxisEntry enum distinguishes GroupHeader from DataItem on grid axes
- expand_category() emits group headers and filters collapsed items
- Grid renders inline group header rows with ▼/▶ indicator
- `z` keybinding toggles collapse of nearest group above cursor

Hide/show item:
- Restore show_item() (was commented out alongside hide_item)
- Add HideItem / ShowItem commands and dispatch
- `H` keybinding hides the current row item
- `:show-item <cat> <item>` command to restore hidden items
- Restore silenced test assertions for hide/show round-trip

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 00:07:11 -07:00
6038cb2d81 refactor: make active_view and axis_of infallible
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>
2026-03-24 09:00:25 -07:00
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
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