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)
This commit is contained in:
Edward Langley
2026-04-05 11:10:41 -07:00
parent 67041dd4a5
commit 19645a34cf
5 changed files with 166 additions and 71 deletions

View File

@ -63,7 +63,7 @@ impl Model {
pub fn add_category(&mut self, name: impl Into<String>) -> Result<CategoryId> {
let name = name.into();
// Count only regular categories for the limit
// Virtuals don't count against the regular category limit
let regular_count = self
.categories
.values()
@ -180,17 +180,8 @@ impl Model {
}
/// Return all category names
/// Names of all regular (non-virtual) categories.
/// Names of all categories (including virtual ones).
pub fn category_names(&self) -> Vec<&str> {
self.categories
.iter()
.filter(|(_, c)| !c.kind.is_virtual())
.map(|(s, _)| s.as_str())
.collect()
}
/// Names of all categories including virtual ones.
pub fn all_category_names(&self) -> Vec<&str> {
self.categories.keys().map(|s| s.as_str()).collect()
}
@ -432,7 +423,8 @@ mod model_tests {
let id1 = m.add_category("Region").unwrap();
let id2 = m.add_category("Region").unwrap();
assert_eq!(id1, id2);
assert_eq!(m.category_names().len(), 1);
// Region + 2 virtuals (_Index, _Dim)
assert_eq!(m.category_names().len(), 3);
}
#[test]
@ -1400,12 +1392,14 @@ mod five_category {
#[test]
fn five_categories_well_within_limit() {
let m = build_model();
assert_eq!(m.category_names().len(), 5);
// 5 regular + 2 virtual (_Index, _Dim)
assert_eq!(m.category_names().len(), 7);
let mut m2 = build_model();
for i in 0..7 {
m2.add_category(format!("Extra{i}")).unwrap();
}
assert_eq!(m2.category_names().len(), 12);
// 12 regular + 2 virtuals = 14
assert_eq!(m2.category_names().len(), 14);
assert!(m2.add_category("OneMore").is_err());
}
}