diff --git a/TAGS b/TAGS
index 40e1640..c22cb0d 100644
--- a/TAGS
+++ b/TAGS
@@ -1,4 +1,2267 @@
 
+crates/improvise-formula/src/lib.rs,49
+pub mod ast;ast1,0
+pub mod parser;parser2,13
+
+crates/improvise-formula/src/ast.rs,1076
+pub enum AggFunc {AggFunc4,86
+    Sum,Sum5,105
+    Avg,Avg6,114
+    Min,Min7,123
+    Max,Max8,132
+    Count,Count9,141
+pub enum BinOp {BinOp17,488
+    Add,Add18,505
+    Sub,Sub19,514
+    Mul,Mul20,523
+    Div,Div21,532
+    Pow,Pow22,541
+    Eq,Eq23,550
+    Ne,Ne24,558
+    Lt,Lt25,566
+    Gt,Gt26,574
+    Le,Le27,582
+    Ge,Ge28,590
+pub struct Filter {Filter32,649
+    pub category: String,category33,669
+    pub item: String,item34,695
+pub enum Expr {Expr38,768
+    Number(f64),Number39,784
+    Ref(String),Ref40,801
+    BinOp(BinOp, Box<Expr>, Box<Expr>),BinOp41,818
+    UnaryMinus(Box<Expr>),UnaryMinus42,858
+    Agg(AggFunc, Box<Expr>, Option<Filter>),Agg43,885
+    If(Box<Expr>, Box<Expr>, Box<Expr>),If44,930
+pub struct Formula {Formula48,1022
+    pub raw: String,raw50,1104
+    pub target: String,target52,1194
+    pub target_category: String,target_category54,1266
+    pub expr: Expr,expr56,1334
+    pub filter: Option<Filter>,filter58,1384
+impl Formula {Formula61,1419
+    pub fn new(new62,1434
+
+crates/improvise-formula/src/parser.rs,7813
+struct FormulaParser;FormulaParser10,210
+const GRAMMAR_INVARIANT: &str = "grammar invariant violated: parser out of sync with formula.pesGRAMMAR_INVARIANT15,440
+pub fn parse_formula(raw: &str, target_category: &str) -> Result<Formula> {parse_formula19,653
+pub fn parse_expr(s: &str) -> Result<Expr> {parse_expr30,1123
+fn build_formula(pair: Pair<Rule>, raw: &str, target_category: &str) -> Formula {build_formula48,1932
+fn build_expr(pair: Pair<Rule>) -> Expr {build_expr72,2674
+fn build_add_expr(pair: Pair<Rule>) -> Expr {build_add_expr77,2784
+fn build_mul_expr(pair: Pair<Rule>) -> Expr {build_mul_expr88,3041
+fn build_pow_expr(pair: Pair<Rule>) -> Expr {build_pow_expr99,3298
+fn build_unary(pair: Pair<Rule>) -> Expr {build_unary112,3734
+fn build_primary(pair: Pair<Rule>) -> Expr {build_primary123,4070
+fn build_agg_call(pair: Pair<Rule>) -> Expr {build_agg_call141,4709
+fn parse_agg_func(s: &str) -> AggFunc {parse_agg_func152,5243
+fn build_if_expr(pair: Pair<Rule>) -> Expr {build_if_expr164,5608
+fn build_comparison(pair: Pair<Rule>) -> Expr {build_comparison173,6049
+fn parse_cmp_op(s: &str) -> BinOp {parse_cmp_op182,6442
+fn build_filter(pair: Pair<Rule>) -> Filter {build_filter195,6765
+fn filter_value_to_string(pair: Pair<Rule>) -> String {filter_value_to_string204,7147
+fn identifier_to_string(pair: Pair<Rule>) -> String {identifier_to_string223,7820
+fn is_pipe_quoted(s: &str) -> bool {is_pipe_quoted232,7996
+fn strip_string_quotes(s: &str) -> String {strip_string_quotes236,8095
+fn unquote_pipe(s: &str) -> String {unquote_pipe244,8476
+fn first_inner(pair: Pair<'_, Rule>) -> Pair<'_, Rule> {first_inner273,9445
+fn fold_left_binop<F, M>(pair: Pair<Rule>, mut build_child: F, match_op: M) -> Exprfold_left_binop281,9821
+mod tests {tests297,10343
+    fn parse_simple_subtraction() {parse_simple_subtraction302,10437
+    fn parse_where_clause() {parse_where_clause310,10718
+    fn parse_sum_aggregation() {parse_sum_aggregation319,11037
+    fn parse_avg_aggregation() {parse_avg_aggregation325,11226
+    fn parse_if_expression() {parse_if_expression331,11413
+    fn parse_numeric_literal() {parse_numeric_literal337,11610
+    fn parse_chained_arithmetic() {parse_chained_arithmetic343,11803
+    fn parse_missing_equals_returns_error() {parse_missing_equals_returns_error348,11922
+    fn parse_min_aggregation() {parse_min_aggregation355,12233
+    fn parse_max_aggregation() {parse_max_aggregation361,12419
+    fn parse_count_aggregation() {parse_count_aggregation367,12605
+    fn parse_sum_with_top_level_where_works() {parse_sum_with_top_level_where_works375,12951
+    fn parse_sum_with_inline_where_filter() {parse_sum_with_inline_where_filter386,13456
+    fn parse_if_with_comparison_operators() {parse_if_with_comparison_operators400,14052
+    fn parse_where_with_quoted_string_inside_expression() {parse_where_with_quoted_string_inside_expression420,14913
+    fn parse_power_operator() {parse_power_operator429,15361
+    fn parse_unary_minus() {parse_unary_minus437,15726
+    fn parse_multiplication() {parse_multiplication445,16050
+    fn parse_division() {parse_division451,16238
+    fn parse_nested_parens() {parse_nested_parens459,16579
+    fn parse_aggregate_name_without_parens_is_ref() {parse_aggregate_name_without_parens_is_ref467,16874
+    fn parse_if_without_parens_is_ref() {parse_if_without_parens_is_ref477,17226
+    fn parse_quoted_string_in_where() {parse_quoted_string_in_where489,17713
+    fn parse_unexpected_token_error() {parse_unexpected_token_error498,18135
+    fn parse_unexpected_character_error() {parse_unexpected_character_error505,18323
+    fn parse_empty_expression_error() {parse_empty_expression_error511,18467
+    fn multi_word_bare_identifier_is_rejected() {multi_word_bare_identifier_is_rejected519,18724
+    fn where_inside_quotes_is_not_a_keyword() {where_inside_quotes_is_not_a_keyword528,19145
+    fn pipe_quoted_identifier_in_expression() {pipe_quoted_identifier_in_expression539,19680
+    fn pipe_quoted_keyword_as_identifier() {pipe_quoted_keyword_as_identifier551,20141
+    fn pipe_quoted_identifier_with_special_chars() {pipe_quoted_identifier_with_special_chars563,20585
+    fn pipe_quoted_in_aggregate() {pipe_quoted_in_aggregate575,21075
+    fn pipe_quoted_in_where_filter_value() {pipe_quoted_in_where_filter_value585,21388
+    fn pipe_quoted_in_inline_where() {pipe_quoted_in_inline_where592,21638
+    fn pipe_quoted_escape_literal_pipe() {pipe_quoted_escape_literal_pipe605,22158
+    fn pipe_quoted_escape_double_backslash() {pipe_quoted_escape_double_backslash612,22410
+    fn pipe_quoted_escape_newline() {pipe_quoted_escape_newline619,22673
+    fn pipe_quoted_unknown_escape_preserved() {pipe_quoted_unknown_escape_preserved626,22924
+    fn mul_binds_tighter_than_add() {mul_binds_tighter_than_add637,23457
+    fn pow_binds_tighter_than_mul() {pow_binds_tighter_than_mul649,23857
+    fn subtraction_is_left_associative() {subtraction_is_left_associative661,24257
+    fn division_is_left_associative() {division_is_left_associative673,24701
+    fn unary_minus_before_pow() {unary_minus_before_pow685,25119
+    fn integer_literal() {integer_literal701,25825
+    fn zero_literal() {zero_literal707,26008
+    fn decimal_literal_without_integer_part() {decimal_literal_without_integer_part713,26171
+    fn decimal_literal_with_trailing_dot() {decimal_literal_with_trailing_dot719,26374
+    fn decimal_literal_with_integer_and_fraction() {decimal_literal_with_integer_and_fraction725,26574
+    fn where_with_bare_identifier_value() {where_with_bare_identifier_value733,26955
+    fn nested_sum_aggregate() {nested_sum_aggregate745,27520
+    fn deeply_nested_parens() {deeply_nested_parens756,27900
+    fn nested_if_expression() {nested_if_expression763,28152
+    fn tolerates_tabs_between_tokens() {tolerates_tabs_between_tokens777,28728
+    fn tolerates_extra_spaces_between_tokens() {tolerates_extra_spaces_between_tokens783,28918
+    fn tolerates_leading_and_trailing_whitespace() {tolerates_leading_and_trailing_whitespace789,29124
+    fn aggregate_function_is_case_insensitive() {aggregate_function_is_case_insensitive797,29474
+    fn if_keyword_is_case_insensitive() {if_keyword_is_case_insensitive807,29872
+    fn where_keyword_is_case_insensitive() {where_keyword_is_case_insensitive813,30057
+    fn target_with_underscore_and_hyphen() {target_with_underscore_and_hyphen821,30420
+    fn hyphen_in_bare_identifier_reference() {hyphen_in_bare_identifier_reference833,30997
+    fn hyphen_in_bare_identifier_inside_aggregate() {hyphen_in_bare_identifier_inside_aggregate844,31402
+    fn pipe_quoted_target_preserves_pipes() {pipe_quoted_target_preserves_pipes855,31809
+mod generator {generator868,32272
+    fn load_grammar() -> HashMap<String, (RuleType, Expr)> {load_grammar875,32482
+    pub struct Gen<'g> {Gen891,33295
+        rules: &'g HashMap<String, (RuleType, Expr)>,rules892,33320
+        choices: Vec<u8>,choices893,33374
+        pos: usize,pos894,33400
+    impl<'g> Gen<'g> {Gen897,33427
+        pub fn new(rules: &'g HashMap<String, (RuleType, Expr)>, choices: Vec<u8>) -> Self {new898,33450
+        fn pick(&mut self) -> u8 {pick906,33659
+        fn emit(&mut self, expr: &Expr, out: &mut String, atomic: bool) {emit912,33816
+        fn emit_ident(&mut self, name: &str, out: &mut String, atomic: bool) {emit_ident969,36368
+        pub fn generate(&mut self, rule_name: &str) -> String {generate1003,37863
+    pub fn formula_string() -> impl Strategy<Value = String> {formula_string1018,38418
+    pub fn expr_string() -> impl Strategy<Value = String> {expr_string1026,38745
+mod grammar_prop_tests {grammar_prop_tests1035,39011
+
+.claude/worktrees/improvise-ewi-formula-crate/crates/improvise-formula/src/lib.rs,49
+pub mod ast;ast1,0
+pub mod parser;parser2,13
+
+.claude/worktrees/improvise-ewi-formula-crate/crates/improvise-formula/src/ast.rs,1076
+pub enum AggFunc {AggFunc4,86
+    Sum,Sum5,105
+    Avg,Avg6,114
+    Min,Min7,123
+    Max,Max8,132
+    Count,Count9,141
+pub enum BinOp {BinOp17,488
+    Add,Add18,505
+    Sub,Sub19,514
+    Mul,Mul20,523
+    Div,Div21,532
+    Pow,Pow22,541
+    Eq,Eq23,550
+    Ne,Ne24,558
+    Lt,Lt25,566
+    Gt,Gt26,574
+    Le,Le27,582
+    Ge,Ge28,590
+pub struct Filter {Filter32,649
+    pub category: String,category33,669
+    pub item: String,item34,695
+pub enum Expr {Expr38,768
+    Number(f64),Number39,784
+    Ref(String),Ref40,801
+    BinOp(BinOp, Box<Expr>, Box<Expr>),BinOp41,818
+    UnaryMinus(Box<Expr>),UnaryMinus42,858
+    Agg(AggFunc, Box<Expr>, Option<Filter>),Agg43,885
+    If(Box<Expr>, Box<Expr>, Box<Expr>),If44,930
+pub struct Formula {Formula48,1022
+    pub raw: String,raw50,1104
+    pub target: String,target52,1194
+    pub target_category: String,target_category54,1266
+    pub expr: Expr,expr56,1334
+    pub filter: Option<Filter>,filter58,1384
+impl Formula {Formula61,1419
+    pub fn new(new62,1434
+
+.claude/worktrees/improvise-ewi-formula-crate/crates/improvise-formula/src/parser.rs,7813
+struct FormulaParser;FormulaParser10,210
+const GRAMMAR_INVARIANT: &str = "grammar invariant violated: parser out of sync with formula.pesGRAMMAR_INVARIANT15,440
+pub fn parse_formula(raw: &str, target_category: &str) -> Result<Formula> {parse_formula19,653
+pub fn parse_expr(s: &str) -> Result<Expr> {parse_expr30,1123
+fn build_formula(pair: Pair<Rule>, raw: &str, target_category: &str) -> Formula {build_formula48,1932
+fn build_expr(pair: Pair<Rule>) -> Expr {build_expr72,2674
+fn build_add_expr(pair: Pair<Rule>) -> Expr {build_add_expr77,2784
+fn build_mul_expr(pair: Pair<Rule>) -> Expr {build_mul_expr88,3041
+fn build_pow_expr(pair: Pair<Rule>) -> Expr {build_pow_expr99,3298
+fn build_unary(pair: Pair<Rule>) -> Expr {build_unary112,3734
+fn build_primary(pair: Pair<Rule>) -> Expr {build_primary123,4070
+fn build_agg_call(pair: Pair<Rule>) -> Expr {build_agg_call141,4709
+fn parse_agg_func(s: &str) -> AggFunc {parse_agg_func152,5243
+fn build_if_expr(pair: Pair<Rule>) -> Expr {build_if_expr164,5608
+fn build_comparison(pair: Pair<Rule>) -> Expr {build_comparison173,6049
+fn parse_cmp_op(s: &str) -> BinOp {parse_cmp_op182,6442
+fn build_filter(pair: Pair<Rule>) -> Filter {build_filter195,6765
+fn filter_value_to_string(pair: Pair<Rule>) -> String {filter_value_to_string204,7147
+fn identifier_to_string(pair: Pair<Rule>) -> String {identifier_to_string223,7820
+fn is_pipe_quoted(s: &str) -> bool {is_pipe_quoted232,7996
+fn strip_string_quotes(s: &str) -> String {strip_string_quotes236,8095
+fn unquote_pipe(s: &str) -> String {unquote_pipe244,8476
+fn first_inner(pair: Pair<'_, Rule>) -> Pair<'_, Rule> {first_inner273,9445
+fn fold_left_binop<F, M>(pair: Pair<Rule>, mut build_child: F, match_op: M) -> Exprfold_left_binop281,9821
+mod tests {tests297,10343
+    fn parse_simple_subtraction() {parse_simple_subtraction302,10437
+    fn parse_where_clause() {parse_where_clause310,10718
+    fn parse_sum_aggregation() {parse_sum_aggregation319,11037
+    fn parse_avg_aggregation() {parse_avg_aggregation325,11226
+    fn parse_if_expression() {parse_if_expression331,11413
+    fn parse_numeric_literal() {parse_numeric_literal337,11610
+    fn parse_chained_arithmetic() {parse_chained_arithmetic343,11803
+    fn parse_missing_equals_returns_error() {parse_missing_equals_returns_error348,11922
+    fn parse_min_aggregation() {parse_min_aggregation355,12233
+    fn parse_max_aggregation() {parse_max_aggregation361,12419
+    fn parse_count_aggregation() {parse_count_aggregation367,12605
+    fn parse_sum_with_top_level_where_works() {parse_sum_with_top_level_where_works375,12951
+    fn parse_sum_with_inline_where_filter() {parse_sum_with_inline_where_filter386,13456
+    fn parse_if_with_comparison_operators() {parse_if_with_comparison_operators400,14052
+    fn parse_where_with_quoted_string_inside_expression() {parse_where_with_quoted_string_inside_expression420,14913
+    fn parse_power_operator() {parse_power_operator429,15361
+    fn parse_unary_minus() {parse_unary_minus437,15726
+    fn parse_multiplication() {parse_multiplication445,16050
+    fn parse_division() {parse_division451,16238
+    fn parse_nested_parens() {parse_nested_parens459,16579
+    fn parse_aggregate_name_without_parens_is_ref() {parse_aggregate_name_without_parens_is_ref467,16874
+    fn parse_if_without_parens_is_ref() {parse_if_without_parens_is_ref477,17226
+    fn parse_quoted_string_in_where() {parse_quoted_string_in_where489,17713
+    fn parse_unexpected_token_error() {parse_unexpected_token_error498,18135
+    fn parse_unexpected_character_error() {parse_unexpected_character_error505,18323
+    fn parse_empty_expression_error() {parse_empty_expression_error511,18467
+    fn multi_word_bare_identifier_is_rejected() {multi_word_bare_identifier_is_rejected519,18724
+    fn where_inside_quotes_is_not_a_keyword() {where_inside_quotes_is_not_a_keyword528,19145
+    fn pipe_quoted_identifier_in_expression() {pipe_quoted_identifier_in_expression539,19680
+    fn pipe_quoted_keyword_as_identifier() {pipe_quoted_keyword_as_identifier551,20141
+    fn pipe_quoted_identifier_with_special_chars() {pipe_quoted_identifier_with_special_chars563,20585
+    fn pipe_quoted_in_aggregate() {pipe_quoted_in_aggregate575,21075
+    fn pipe_quoted_in_where_filter_value() {pipe_quoted_in_where_filter_value585,21388
+    fn pipe_quoted_in_inline_where() {pipe_quoted_in_inline_where592,21638
+    fn pipe_quoted_escape_literal_pipe() {pipe_quoted_escape_literal_pipe605,22158
+    fn pipe_quoted_escape_double_backslash() {pipe_quoted_escape_double_backslash612,22410
+    fn pipe_quoted_escape_newline() {pipe_quoted_escape_newline619,22673
+    fn pipe_quoted_unknown_escape_preserved() {pipe_quoted_unknown_escape_preserved626,22924
+    fn mul_binds_tighter_than_add() {mul_binds_tighter_than_add637,23457
+    fn pow_binds_tighter_than_mul() {pow_binds_tighter_than_mul649,23857
+    fn subtraction_is_left_associative() {subtraction_is_left_associative661,24257
+    fn division_is_left_associative() {division_is_left_associative673,24701
+    fn unary_minus_before_pow() {unary_minus_before_pow685,25119
+    fn integer_literal() {integer_literal701,25825
+    fn zero_literal() {zero_literal707,26008
+    fn decimal_literal_without_integer_part() {decimal_literal_without_integer_part713,26171
+    fn decimal_literal_with_trailing_dot() {decimal_literal_with_trailing_dot719,26374
+    fn decimal_literal_with_integer_and_fraction() {decimal_literal_with_integer_and_fraction725,26574
+    fn where_with_bare_identifier_value() {where_with_bare_identifier_value733,26955
+    fn nested_sum_aggregate() {nested_sum_aggregate745,27520
+    fn deeply_nested_parens() {deeply_nested_parens756,27900
+    fn nested_if_expression() {nested_if_expression763,28152
+    fn tolerates_tabs_between_tokens() {tolerates_tabs_between_tokens777,28728
+    fn tolerates_extra_spaces_between_tokens() {tolerates_extra_spaces_between_tokens783,28918
+    fn tolerates_leading_and_trailing_whitespace() {tolerates_leading_and_trailing_whitespace789,29124
+    fn aggregate_function_is_case_insensitive() {aggregate_function_is_case_insensitive797,29474
+    fn if_keyword_is_case_insensitive() {if_keyword_is_case_insensitive807,29872
+    fn where_keyword_is_case_insensitive() {where_keyword_is_case_insensitive813,30057
+    fn target_with_underscore_and_hyphen() {target_with_underscore_and_hyphen821,30420
+    fn hyphen_in_bare_identifier_reference() {hyphen_in_bare_identifier_reference833,30997
+    fn hyphen_in_bare_identifier_inside_aggregate() {hyphen_in_bare_identifier_inside_aggregate844,31402
+    fn pipe_quoted_target_preserves_pipes() {pipe_quoted_target_preserves_pipes855,31809
+mod generator {generator868,32272
+    fn load_grammar() -> HashMap<String, (RuleType, Expr)> {load_grammar875,32482
+    pub struct Gen<'g> {Gen891,33295
+        rules: &'g HashMap<String, (RuleType, Expr)>,rules892,33320
+        choices: Vec<u8>,choices893,33374
+        pos: usize,pos894,33400
+    impl<'g> Gen<'g> {Gen897,33427
+        pub fn new(rules: &'g HashMap<String, (RuleType, Expr)>, choices: Vec<u8>) -> Self {new898,33450
+        fn pick(&mut self) -> u8 {pick906,33659
+        fn emit(&mut self, expr: &Expr, out: &mut String, atomic: bool) {emit912,33816
+        fn emit_ident(&mut self, name: &str, out: &mut String, atomic: bool) {emit_ident969,36368
+        pub fn generate(&mut self, rule_name: &str) -> String {generate1003,37863
+    pub fn formula_string() -> impl Strategy<Value = String> {formula_string1018,38418
+    pub fn expr_string() -> impl Strategy<Value = String> {expr_string1026,38745
+mod grammar_prop_tests {grammar_prop_tests1035,39011
+
+.claude/worktrees/improvise-ewi-formula-crate/crates/improvise-core/src/format.rs,2444
+pub fn format_value(v: Option<&CellValue>, comma: bool, decimals: u8) -> String {format_value4,103
+pub fn parse_number_format(fmt: &str) -> (bool, u8) {parse_number_format14,493
+fn round_half_away(n: f64, decimals: u8) -> f64 {round_half_away24,797
+pub fn format_f64(n: f64, comma: bool, decimals: u8) -> String {format_f6430,1017
+mod tests {tests61,1935
+    fn parse_comma_and_zero_decimals() {parse_comma_and_zero_decimals67,2146
+    fn parse_comma_and_two_decimals() {parse_comma_and_two_decimals72,2265
+    fn parse_no_comma_two_decimals() {parse_no_comma_two_decimals77,2383
+    fn parse_comma_only() {parse_comma_only82,2500
+    fn parse_empty_string() {parse_empty_string87,2604
+    fn parse_dot_no_digits_after() {parse_dot_no_digits_after92,2710
+    fn parse_multiple_dots_uses_last() {parse_multiple_dots_uses_last98,2901
+    fn format_no_comma_zero_decimals() {format_no_comma_zero_decimals106,3232
+    fn format_no_comma_two_decimals() {format_no_comma_two_decimals111,3350
+    fn format_comma_zero_decimals() {format_comma_zero_decimals116,3470
+    fn format_comma_two_decimals() {format_comma_two_decimals121,3585
+    fn format_comma_exactly_three_digits() {format_comma_exactly_three_digits128,3857
+    fn format_comma_four_digits() {format_comma_four_digits133,3976
+    fn format_comma_seven_digits() {format_comma_seven_digits138,4089
+    fn format_comma_millions_with_decimals() {format_comma_millions_with_decimals143,4210
+    fn format_negative_with_comma() {format_negative_with_comma150,4519
+    fn format_negative_with_comma_and_decimals() {format_negative_with_comma_and_decimals155,4636
+    fn format_negative_no_comma() {format_negative_no_comma160,4777
+    fn format_zero() {format_zero167,5074
+    fn format_small_fraction() {format_small_fraction172,5170
+    fn format_negative_small_fraction() {format_negative_small_fraction177,5278
+    fn round_half_up_positive() {round_half_up_positive184,5539
+    fn round_half_down_negative() {round_half_down_negative190,5696
+    fn round_half_at_one_decimal() {round_half_at_one_decimal196,5853
+    fn round_below_half_truncates() {round_below_half_truncates202,5988
+    fn round_above_half_rounds_up() {round_above_half_rounds_up207,6100
+    fn format_value_number() {format_value_number214,6376
+    fn format_value_text() {format_value_text220,6531
+    fn format_value_none() {format_value_none226,6690
+
+.claude/worktrees/improvise-ewi-formula-crate/crates/improvise-core/src/lib.rs,117
+pub mod format;format9,383
+pub mod model;model10,399
+pub mod view;view11,414
+pub mod workbook;workbook12,428
+
+.claude/worktrees/improvise-ewi-formula-crate/crates/improvise-core/src/workbook.rs,2132
+pub struct Workbook {Workbook20,801
+    pub model: Model,model21,823
+    pub views: IndexMap<String, View>,views22,845
+    pub active_view: String,active_view23,884
+impl Workbook {Workbook26,916
+    pub fn new(name: impl Into<String>) -> Self {new35,1490
+    pub fn add_category(&mut self, name: impl Into<String>) -> Result<CategoryId> {add_category56,2284
+    pub fn add_label_category(&mut self, name: impl Into<String>) -> Result<CategoryId> {add_label_category67,2691
+    pub fn remove_category(&mut self, name: &str) {remove_category78,3098
+    pub fn active_view(&self) -> &View {active_view87,3484
+    pub fn active_view_mut(&mut self) -> &mut View {active_view_mut93,3652
+    pub fn create_view(&mut self, name: impl Into<String>) -> &mut View {create_view103,4176
+    pub fn switch_view(&mut self, name: &str) -> Result<()> {switch_view113,4538
+    pub fn delete_view(&mut self, name: &str) -> Result<()> {delete_view122,4797
+    pub fn normalize_view_state(&mut self) {normalize_view_state135,5294
+mod tests {tests145,5484
+    fn new_workbook_has_default_view_with_virtuals_seeded() {new_workbook_has_default_view_with_virtuals_seeded150,5561
+    fn add_category_notifies_all_views() {add_category_notifies_all_views163,6119
+    fn add_label_category_sets_none_axis_on_all_views() {add_label_category_sets_none_axis_on_all_views173,6520
+    fn remove_category_removes_from_all_views() {remove_category_removes_from_all_views182,6884
+    fn switch_view_changes_active_view() {switch_view_changes_active_view207,7609
+    fn switch_view_unknown_returns_error() {switch_view_unknown_returns_error215,7835
+    fn delete_view_removes_it() {delete_view_removes_it221,7999
+    fn delete_last_view_returns_error() {delete_last_view_returns_error229,8221
+    fn delete_active_view_switches_to_another() {delete_active_view_switches_to_another237,8475
+    fn first_category_goes_to_row_second_to_column_rest_to_page() {first_category_goes_to_row_second_to_column_rest_to_page246,8750
+    fn create_view_copies_category_structure() {create_view_copies_category_structure258,9205
+
+.claude/worktrees/improvise-ewi-formula-crate/crates/improvise-core/src/model/symbol.rs,879
+pub struct Symbol(u64);Symbol5,171
+pub struct SymbolTable {SymbolTable9,274
+    to_id: HashMap<String, Symbol>,to_id10,299
+    to_str: Vec<String>,to_str11,335
+impl SymbolTable {SymbolTable14,363
+    pub fn new() -> Self {new16,406
+    pub fn intern(&mut self, s: &str) -> Symbol {intern22,564
+    pub fn get(&self, s: &str) -> Option<Symbol> {get33,909
+    pub fn resolve(&self, sym: Symbol) -> &str {resolve38,1047
+    pub fn intern_pair(&mut self, cat: &str, item: &str) -> (Symbol, Symbol) {intern_pair43,1180
+    pub fn intern_coords(&mut self, coords: &[(String, String)]) -> Vec<(Symbol, Symbol)> {intern_coords48,1351
+mod tests {tests54,1534
+    fn intern_returns_same_id() {intern_returns_same_id58,1577
+    fn different_strings_different_ids() {different_strings_different_ids66,1766
+    fn resolve_roundtrips() {resolve_roundtrips74,1964
+
+.claude/worktrees/improvise-ewi-formula-crate/crates/improvise-core/src/model/types.rs,9014
+const MAX_CATEGORIES: usize = 12;MAX_CATEGORIES11,260
+pub struct Model {Model23,935
+    pub name: String,name24,954
+    pub categories: IndexMap<String, Category>,categories25,976
+    pub data: DataStore,data26,1024
+    formulas: Vec<Formula>,formulas27,1049
+    next_category_id: CategoryId,next_category_id28,1077
+    pub measure_agg: HashMap<String, AggFunc>,measure_agg32,1282
+    formula_cache: HashMap<CellKey, CellValue>,formula_cache35,1427
+impl Model {Model38,1478
+    pub fn new(name: impl Into<String>) -> Self {new39,1491
+    pub fn add_category(&mut self, name: impl Into<String>) -> Result<CategoryId> {add_category70,2652
+    pub fn add_label_category(&mut self, name: impl Into<String>) -> Result<CategoryId> {add_label_category95,3676
+    pub fn remove_category(&mut self, name: &str) {remove_category111,4350
+    pub fn remove_item(&mut self, cat_name: &str, item_name: &str) {remove_item131,5040
+    pub fn category_mut(&mut self, name: &str) -> Option<&mut Category> {category_mut146,5507
+    pub fn category(&self, name: &str) -> Option<&Category> {category150,5626
+    pub fn set_cell(&mut self, key: CellKey, value: CellValue) {set_cell154,5729
+    pub fn clear_cell(&mut self, key: &CellKey) {clear_cell158,5836
+    pub fn get_cell(&self, key: &CellKey) -> Option<&CellValue> {get_cell162,5924
+    pub fn add_formula(&mut self, formula: Formula) {add_formula166,6024
+    pub fn remove_formula(&mut self, target: &str, target_category: &str) {remove_formula185,6808
+    pub fn formulas(&self) -> &[Formula] {formulas190,7001
+    pub fn category_names(&self) -> Vec<&str> {category_names196,7166
+    pub fn measure_item_names(&self) -> Vec<String> {measure_item_names204,7563
+    pub fn effective_item_names(&self, cat_name: &str) -> Vec<String> {effective_item_names225,8295
+    pub fn regular_category_names(&self) -> Vec<&str> {regular_category_names241,8820
+    const MAX_EVAL_DEPTH: u8 = 16;MAX_EVAL_DEPTH253,9314
+    pub fn evaluate(&self, key: &CellKey) -> Option<CellValue> {evaluate255,9350
+    fn evaluate_depth(&self, key: &CellKey, depth: u8) -> Option<CellValue> {evaluate_depth259,9477
+    pub fn evaluate_aggregated(&self, key: &CellKey, none_cats: &[String]) -> Option<CellValue> evaluate_aggregated277,10231
+    pub fn evaluate_aggregated_f64(&self, key: &CellKey, none_cats: &[String]) -> f64 {evaluate_aggregated_f64316,11560
+    pub fn recompute_formulas(&mut self, none_cats: &[String]) {recompute_formulas330,12206
+    fn evaluate_formula_cell(&self, key: &CellKey, none_cats: &[String]) -> Option<CellValue> {evaluate_formula_cell388,14521
+    fn eval_formula_with_cache(eval_formula_with_cache401,15036
+        fn find_item_category<'a>(model: &'a Model, item_name: &str) -> Option<&'a str> {find_item_category420,15605
+        fn eval_expr_cached(eval_expr_cached436,16234
+        fn eval_bool_cached(eval_bool_cached542,21033
+    fn aggregate_raw(&self, key: &CellKey, none_cats: &[String]) -> Option<CellValue> {aggregate_raw581,22420
+    fn eval_formula_depth(eval_formula_depth608,23396
+        fn find_item_category<'a>(model: &'a Model, item_name: &str) -> Option<&'a str> {find_item_category627,23937
+        fn eval_expr(eval_expr646,24771
+        fn eval_bool(eval_bool736,28821
+mod model_tests {model_tests778,30222
+    fn coord(pairs: &[(&str, &str)]) -> CellKey {coord782,30313
+    fn add_category_creates_entry() {add_category_creates_entry792,30546
+    fn add_category_duplicate_is_idempotent() {add_category_duplicate_is_idempotent799,30735
+    fn add_category_max_limit() {add_category_max_limit809,31083
+    fn set_and_get_cell_roundtrip() {set_and_get_cell_roundtrip818,31320
+    fn get_unset_cell_returns_empty() {get_unset_cell_returns_empty828,31702
+    fn overwrite_cell() {overwrite_cell835,31885
+    fn three_category_model_independent_cells() {three_category_model_independent_cells844,32193
+    fn remove_category_deletes_category_and_cells() {remove_category_deletes_category_and_cells880,33508
+    fn evaluate_aggregated_sums_over_hidden_dimension() {evaluate_aggregated_sums_over_hidden_dimension901,34248
+    fn measure_item_names_includes_formula_targets() {measure_item_names_includes_formula_targets942,35807
+    fn evaluate_aggregated_no_hidden_delegates_to_evaluate() {evaluate_aggregated_no_hidden_delegates_to_evaluate973,36918
+    fn evaluate_aggregated_respects_measure_agg() {evaluate_aggregated_respects_measure_agg986,37385
+mod formula_tests {formula_tests1017,38524
+    fn coord(pairs: &[(&str, &str)]) -> CellKey {coord1022,38656
+    fn approx_eq(a: f64, b: f64) -> bool {approx_eq1031,38877
+    fn revenue_cost_model() -> Model {revenue_cost_model1035,38956
+    fn profit_equals_revenue_minus_cost() {profit_equals_revenue_minus_cost1068,39996
+    fn formula_evaluates_per_region() {formula_evaluates_per_region1076,40325
+    fn formula_multiplication() {formula_multiplication1086,40800
+    fn formula_division() {formula_division1100,41271
+    fn division_by_zero_yields_empty() {division_by_zero_yields_empty1116,41868
+    fn unary_minus() {unary_minus1142,42820
+    fn power_operator() {power_operator1153,43238
+    fn formula_with_missing_ref_returns_empty() {formula_with_missing_ref_returns_empty1169,43795
+    fn circular_formula_returns_error() {circular_formula_returns_error1185,44426
+    fn self_referencing_formula_returns_error() {self_referencing_formula_returns_error1204,45136
+    fn formula_where_applied_to_matching_region() {formula_where_applied_to_matching_region1220,45691
+    fn formula_where_not_applied_to_non_matching_region() {formula_where_not_applied_to_non_matching_region1236,46237
+    fn add_formula_replaces_same_target() {add_formula_replaces_same_target1251,46719
+    fn remove_formula() {remove_formula1261,47181
+    fn sum_aggregation_across_region() {sum_aggregation_across_region1271,47554
+    fn count_aggregation() {count_aggregation1286,48095
+    fn if_true_branch() {if_true_branch1309,48886
+    fn if_false_branch() {if_false_branch1325,49441
+    fn where_filter_absent_category_does_not_apply_formula() {where_filter_absent_category_does_not_apply_formula1350,50681
+    fn sum_inner_expression_constrains_which_cells_are_summed() {sum_inner_expression_constrains_which_cells_are_summed1375,51946
+    fn add_formula_same_target_name_different_category_both_coexist() {add_formula_same_target_name_different_category_both_coexist1409,53326
+    fn add_formula_same_target_name_different_category_evaluates_independently() {add_formula_same_target_name_different_category_evaluates_independently1431,54205
+    fn formula_chain_preserves_full_precision() {formula_chain_preserves_full_precision1463,55438
+    fn remove_formula_only_removes_specified_target_category() {remove_formula_only_removes_specified_target_category1512,57488
+mod five_category {five_category1540,58402
+    const DATA: &[(&str, &str, &str, &str, f64, f64)] = &[DATA1547,58596
+    fn coord(region: &str, product: &str, channel: &str, time: &str, measure: &str) -> CellKey {coord1566,59587
+    fn build_model() -> Model {build_model1576,60012
+    fn build_workbook() -> Workbook {build_workbook1619,61749
+    fn approx(a: f64, b: f64) -> bool {approx1662,63384
+    fn all_sixteen_revenue_cells_stored() {all_sixteen_revenue_cells_stored1667,63472
+    fn all_sixteen_cost_cells_stored() {all_sixteen_cost_cells_stored1677,63760
+    fn spot_check_raw_revenue() {spot_check_raw_revenue1687,64042
+    fn distinct_cells_do_not_alias() {distinct_cells_do_not_alias1700,64431
+    fn profit_formula_correct_at_every_intersection() {profit_formula_correct_at_every_intersection1708,64707
+    fn margin_formula_correct_at_every_intersection() {margin_formula_correct_at_every_intersection1724,65350
+    fn chained_formula_profit_feeds_margin() {chained_formula_profit_feeds_margin1740,66007
+    fn update_revenue_updates_profit_and_margin() {update_revenue_updates_profit_and_margin1750,66330
+    fn sum_revenue_for_east_region() {sum_revenue_for_east_region1769,67025
+    fn sum_revenue_for_online_channel() {sum_revenue_for_online_channel1785,67603
+    fn sum_revenue_for_shirts_q1() {sum_revenue_for_shirts_q11801,68191
+    fn sum_all_revenue_equals_grand_total() {sum_all_revenue_equals_grand_total1818,68837
+    fn default_view_first_two_on_axes_rest_on_page() {default_view_first_two_on_axes_rest_on_page1827,69248
+    fn rearranging_axes_does_not_affect_data() {rearranging_axes_does_not_affect_data1838,69659
+    fn two_views_have_independent_axis_assignments() {two_views_have_independent_axis_assignments1856,70239
+    fn page_selections_are_per_view() {page_selections_are_per_view1879,71009
+    fn five_categories_well_within_limit() {five_categories_well_within_limit1896,71511
+mod prop_tests {prop_tests1911,72023
+    fn finite_f64() -> impl Strategy<Value = f64> {finite_f641917,72182
+
+.claude/worktrees/improvise-ewi-formula-crate/crates/improvise-core/src/model/cell.rs,4379
+pub struct CellKey(pub Vec<(String, String)>);CellKey10,335
+impl CellKey {CellKey12,383
+    pub fn new(mut coords: Vec<(String, String)>) -> Self {new13,398
+    pub fn get(&self, category: &str) -> Option<&str> {get18,532
+    pub fn with(mut self, category: impl Into<String>, item: impl Into<String>) -> Self {with25,710
+    pub fn without(&self, category: &str) -> Self {without37,1105
+    pub fn matches_partial(&self, partial: &[(String, String)]) -> bool {matches_partial48,1357
+impl std::fmt::Display for CellKey {CellKey55,1545
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {fmt56,1582
+pub enum CellValue {CellValue63,1855
+    Number(f64),Number64,1876
+    Text(String),Text65,1893
+    Error(String),Error67,1979
+impl CellValue {CellValue70,2001
+    pub fn as_f64(&self) -> Option<f64> {as_f6471,2018
+    pub fn is_error(&self) -> bool {is_error78,2167
+impl std::fmt::Display for CellValue {CellValue83,2257
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {fmt84,2296
+pub struct InternedKey(pub Vec<(Symbol, Symbol)>);InternedKey102,2943
+pub struct DataStore {DataStore107,3176
+    cells: IndexMap<InternedKey, CellValue>,cells110,3333
+    pub symbols: SymbolTable,symbols112,3449
+    index: HashMap<(Symbol, Symbol), HashSet<InternedKey>>,index114,3556
+impl Serialize for DataStore {DataStore117,3619
+    fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {serialize118,3650
+impl<'de> Deserialize<'de> for DataStore {DataStore129,4006
+    fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {deserialize130,4049
+impl DataStore {DataStore140,4354
+    pub fn new() -> Self {new141,4371
+    pub fn intern_key(&mut self, key: &CellKey) -> InternedKey {intern_key146,4475
+    pub fn to_cell_key(&self, ikey: &InternedKey) -> CellKey {to_cell_key151,4667
+    pub fn sort_by_key(&mut self) {sort_by_key168,5243
+    pub fn set(&mut self, key: CellKey, value: CellValue) {set185,5794
+    pub fn get(&self, key: &CellKey) -> Option<&CellValue> {get194,6103
+    fn lookup_key(&self, key: &CellKey) -> Option<InternedKey> {lookup_key200,6319
+    pub fn iter_cells(&self) -> impl Iterator<Item = (CellKey, &CellValue)> {iter_cells210,6684
+    pub fn remove(&mut self, key: &CellKey) {remove214,6834
+    pub fn matching_values(&self, partial: &[(String, String)]) -> Vec<&CellValue> {matching_values229,7336
+    pub fn matching_cells(&self, partial: &[(String, String)]) -> Vec<(CellKey, &CellValue)> {matching_cells268,8592
+mod cell_key {cell_key307,9712
+    fn key(pairs: &[(&str, &str)]) -> CellKey {key310,9752
+    fn coords_are_sorted_by_category_name() {coords_are_sorted_by_category_name320,9983
+    fn get_returns_item_for_known_category() {get_returns_item_for_known_category332,10308
+    fn get_returns_none_for_unknown_category() {get_returns_none_for_unknown_category339,10546
+    fn with_adds_new_coordinate_in_sorted_order() {with_adds_new_coordinate_in_sorted_order345,10702
+    fn with_replaces_existing_coordinate() {with_replaces_existing_coordinate354,11031
+    fn without_removes_coordinate() {without_removes_coordinate361,11270
+    fn without_missing_category_is_noop() {without_missing_category_is_noop369,11543
+    fn matches_partial_full_match() {matches_partial_full_match375,11703
+    fn matches_partial_empty_matches_all() {matches_partial_empty_matches_all382,11945
+    fn matches_partial_wrong_item_no_match() {matches_partial_wrong_item_no_match388,12117
+    fn matches_partial_missing_category_no_match() {matches_partial_missing_category_no_match395,12369
+    fn display_format() {display_format402,12607
+mod data_store {data_store409,12751
+    fn key(pairs: &[(&str, &str)]) -> CellKey {key412,12817
+    fn get_missing_returns_empty() {get_missing_returns_empty422,13048
+    fn set_and_get_roundtrip() {set_and_get_roundtrip428,13208
+    fn overwrite_value() {overwrite_value436,13491
+    fn remove_evicts_key() {remove_evicts_key445,13799
+    fn matching_cells_returns_correct_subset() {matching_cells_returns_correct_subset454,14067
+mod prop_tests {prop_tests478,14907
+    fn pairs_map() -> impl Strategy<Value = Vec<(String, String)>> {pairs_map483,15088
+    fn finite_f64() -> impl Strategy<Value = f64> {finite_f64489,15336
+
+.claude/worktrees/improvise-ewi-formula-crate/crates/improvise-core/src/model/mod.rs,109
+pub mod category;category1,0
+pub mod cell;cell2,18
+pub mod symbol;symbol3,32
+pub mod types;types4,48
+
+.claude/worktrees/improvise-ewi-formula-crate/crates/improvise-core/src/model/category.rs,2432
+pub type CategoryId = usize;CategoryId4,62
+pub type ItemId = usize;ItemId5,91
+pub struct Item {Item8,165
+    pub id: ItemId,id9,183
+    pub name: String,name10,203
+    pub group: Option<String>,group12,259
+impl Item {Item15,293
+    pub fn new(id: ItemId, name: impl Into<String>) -> Self {new16,305
+    pub fn with_group(mut self, group: impl Into<String>) -> Self {with_group24,471
+pub struct Group {Group31,650
+    pub name: String,name32,669
+    pub parent: Option<String>,parent34,740
+impl Group {Group37,775
+    pub fn new(name: impl Into<String>) -> Self {new38,788
+    pub fn with_parent(mut self, parent: impl Into<String>) -> Self {with_parent45,927
+pub enum CategoryKind {CategoryKind55,1302
+    Regular,Regular57,1341
+    VirtualIndex,VirtualIndex59,1424
+    VirtualDim,VirtualDim61,1507
+    VirtualMeasure,VirtualMeasure67,1882
+    Label,Label72,2156
+impl CategoryKind {CategoryKind75,2170
+    pub fn is_regular(&self) -> bool {is_regular78,2312
+pub struct Category {Category84,2454
+    pub id: CategoryId,id85,2476
+    pub name: String,name86,2500
+    pub items: IndexMap<String, Item>,items88,2555
+    pub groups: Vec<Group>,groups90,2633
+    next_item_id: ItemId,next_item_id92,2690
+    pub kind: CategoryKind,kind95,2792
+impl Category {Category98,2823
+    pub fn new(id: CategoryId, name: impl Into<String>) -> Self {new99,2839
+    pub fn with_kind(mut self, kind: CategoryKind) -> Self {with_kind110,3122
+    pub fn add_item(&mut self, name: impl Into<String>) -> ItemId {add_item115,3229
+    pub fn remove_item(&mut self, name: &str) {remove_item126,3567
+    pub fn add_item_in_group(add_item_in_group130,3661
+    pub fn add_group(&mut self, group: Group) {add_group147,4130
+    pub fn ordered_item_names(&self) -> Vec<&str> {ordered_item_names154,4355
+mod tests {tests160,4485
+    fn cat() -> Category {cat163,4532
+    fn add_item_returns_sequential_ids() {add_item_returns_sequential_ids168,4613
+    fn add_item_duplicate_returns_same_id() {add_item_duplicate_returns_same_id177,4834
+    fn add_item_in_group_sets_group() {add_item_in_group_sets_group186,5070
+    fn add_item_in_group_duplicate_returns_same_id() {add_item_in_group_duplicate_returns_same_id196,5317
+    fn add_group_deduplicates() {add_group_deduplicates205,5590
+    fn item_index_reflects_insertion_order() {item_index_reflects_insertion_order213,5787
+
+.claude/worktrees/improvise-ewi-formula-crate/crates/improvise-core/src/view/types.rs,3892
+fn default_prune() -> bool {default_prune7,128
+pub struct View {View12,217
+    pub name: String,name13,235
+    pub category_axes: IndexMap<String, Axis>,category_axes15,299
+    pub page_selections: HashMap<String, String>,page_selections17,396
+    pub hidden_items: HashMap<String, HashSet<String>>,hidden_items19,480
+    pub collapsed_groups: HashMap<String, HashSet<String>>,collapsed_groups21,574
+    pub number_format: String,number_format23,705
+    pub prune_empty: bool,prune_empty26,843
+    pub row_offset: usize,row_offset28,901
+    pub col_offset: usize,col_offset29,928
+    pub selected: (usize, usize),selected31,996
+impl View {View34,1033
+    pub fn new(name: impl Into<String>) -> Self {new35,1045
+    pub fn on_category_added(&mut self, cat_name: &str) {on_category_added50,1497
+    pub fn on_category_removed(&mut self, cat_name: &str) {on_category_removed102,3711
+    pub fn set_axis(&mut self, cat_name: &str, axis: Axis) {set_axis108,3920
+    pub fn axis_of(&self, cat_name: &str) -> Axis {axis_of114,4085
+    pub fn categories_on(&self, axis: Axis) -> Vec<&str> {categories_on121,4293
+    pub fn none_cats(&self) -> Vec<String> {none_cats132,4743
+    pub fn set_page_selection(&mut self, cat_name: &str, item: &str) {set_page_selection139,4913
+    pub fn page_selection(&self, cat_name: &str) -> Option<&str> {page_selection144,5081
+    pub fn toggle_group_collapse(&mut self, cat_name: &str, group_name: &str) {toggle_group_collapse148,5218
+    pub fn is_group_collapsed(&self, cat_name: &str, group_name: &str) -> bool {is_group_collapsed160,5575
+    pub fn hide_item(&mut self, cat_name: &str, item_name: &str) {hide_item167,5795
+    pub fn show_item(&mut self, cat_name: &str, item_name: &str) {show_item174,6006
+    pub fn is_hidden(&self, cat_name: &str, item_name: &str) -> bool {is_hidden180,6190
+    pub fn transpose_axes(&mut self) {transpose_axes189,6511
+    pub fn cycle_axis(&mut self, cat_name: &str) {cycle_axis212,7199
+mod tests {tests227,7616
+    fn view_with_cats(cats: &[&str]) -> View {view_with_cats231,7677
+    fn first_category_assigned_to_row() {first_category_assigned_to_row240,7863
+    fn second_category_assigned_to_column() {second_category_assigned_to_column246,8021
+    fn third_and_later_categories_assigned_to_page() {third_and_later_categories_assigned_to_page252,8198
+    fn set_axis_changes_assignment() {set_axis_changes_assignment259,8454
+    fn categories_on_returns_correct_list() {categories_on_returns_correct_list266,8671
+    fn transpose_axes_swaps_row_and_column() {transpose_axes_swaps_row_and_column274,8995
+    fn transpose_axes_leaves_page_categories_unchanged() {transpose_axes_leaves_page_categories_unchanged285,9412
+    fn transpose_axes_is_its_own_inverse() {transpose_axes_is_its_own_inverse293,9695
+    fn axis_of_unknown_category_panics() {axis_of_unknown_category_panics303,10060
+    fn page_selection_set_and_get() {page_selection_set_and_get309,10185
+    fn toggle_group_collapse_toggles_twice() {toggle_group_collapse_toggles_twice317,10466
+    fn is_group_collapsed_isolated_across_categories() {is_group_collapsed_isolated_across_categories327,10826
+    fn is_group_collapsed_isolated_across_groups() {is_group_collapsed_isolated_across_groups334,11042
+    fn hide_and_show_item() {hide_and_show_item341,11254
+    fn cycle_axis_row_to_column() {cycle_axis_row_to_column351,11566
+    fn cycle_axis_column_to_page() {cycle_axis_column_to_page358,11768
+    fn cycle_axis_page_to_none() {cycle_axis_page_to_none366,12016
+    fn cycle_axis_none_to_row() {cycle_axis_none_to_row373,12219
+    fn cycle_axis_resets_scroll_and_selection() {cycle_axis_resets_scroll_and_selection381,12460
+mod prop_tests {prop_tests394,12808
+    fn unique_cat_names() -> impl Strategy<Value = Vec<String>> {unique_cat_names399,12904
+
+.claude/worktrees/improvise-ewi-formula-crate/crates/improvise-core/src/view/layout.rs,5064
+pub fn synthetic_record_info(key: &CellKey) -> Option<(usize, String)> {synthetic_record_info10,284
+pub enum AxisEntry {AxisEntry22,781
+    GroupHeader {GroupHeader23,802
+    DataItem(Vec<String>),DataItem27,881
+pub struct GridLayout {GridLayout35,1229
+    pub row_cats: Vec<String>,row_cats36,1253
+    pub col_cats: Vec<String>,col_cats37,1284
+    pub page_coords: Vec<(String, String)>,page_coords38,1315
+    pub row_items: Vec<AxisEntry>,row_items39,1359
+    pub col_items: Vec<AxisEntry>,col_items40,1394
+    pub none_cats: Vec<String>,none_cats42,1499
+    pub records: Option<Rc<Vec<(CellKey, CellValue)>>>,records45,1652
+impl GridLayout {GridLayout48,1711
+    pub fn with_frozen_records(with_frozen_records51,1875
+    pub fn new(model: &Model, view: &View) -> Self {new72,2530
+    fn build_records_mode(build_records_mode129,4478
+    pub fn records_display(&self, row: usize, col: usize) -> Option<String> {records_display186,6448
+    pub fn prune_empty(&mut self, model: &Model) {prune_empty211,7417
+    pub fn is_records_mode(&self) -> bool {is_records_mode303,10777
+    pub fn row_count(&self) -> usize {row_count308,10913
+    pub fn col_count(&self) -> usize {col_count316,11141
+    pub fn row_label(&self, row: usize) -> String {row_label323,11312
+    pub fn col_label(&self, col: usize) -> String {col_label338,11696
+    pub fn resolve_display(&self, key: &CellKey) -> Option<String> {resolve_display355,12206
+    pub fn display_text(display_text369,12808
+    pub fn cell_key(&self, row: usize, col: usize) -> Option<CellKey> {cell_key390,13592
+    pub fn data_row_to_visual(&self, data_row: usize) -> Option<usize> {data_row_to_visual437,15104
+    pub fn data_col_to_visual(&self, data_col: usize) -> Option<usize> {data_col_to_visual451,15555
+    pub fn row_group_for(&self, data_row: usize) -> Option<(String, String)> {row_group_for466,16067
+    pub fn col_group_for(&self, data_col: usize) -> Option<(String, String)> {col_group_for483,16655
+fn expand_category(expand_category502,17304
+fn cross_product(model: &Model, view: &View, cats: &[String]) -> Vec<AxisEntry> {cross_product552,18925
+mod tests {tests570,19519
+    fn records_workbook() -> Workbook {records_workbook576,19707
+    fn prune_empty_removes_all_empty_columns_in_pivot_mode() {prune_empty_removes_all_empty_columns_in_pivot_mode606,20731
+    fn records_mode_activated_when_index_and_dim_on_axes() {records_mode_activated_when_index_and_dim_on_axes627,21622
+    fn records_mode_cell_key_returns_synthetic_for_all_columns() {records_mode_cell_key_returns_synthetic_for_all_columns638,22028
+    fn records_mode_resolve_display_returns_values() {records_mode_resolve_display_returns_values661,23024
+    fn synthetic_record_info_returns_none_for_pivot_keys() {synthetic_record_info_returns_none_for_pivot_keys688,24122
+    fn synthetic_record_info_extracts_index_and_dim() {synthetic_record_info_extracts_index_and_dim697,24421
+    fn records_mode_includes_measure_in_dim_columns() {records_mode_includes_measure_in_dim_columns711,24977
+    fn records_mode_initial_layout_is_sorted() {records_mode_initial_layout_is_sorted739,26049
+    fn records_mode_new_record_appends_at_bottom() {records_mode_new_record_appends_at_bottom778,27681
+    fn coord(pairs: &[(&str, &str)]) -> CellKey {coord809,29089
+    fn two_cat_workbook() -> Workbook {two_cat_workbook818,29310
+    fn row_and_col_counts_match_item_counts() {row_and_col_counts_match_item_counts832,29741
+    fn cell_key_encodes_correct_coordinates() {cell_key_encodes_correct_coordinates840,30028
+    fn cell_key_out_of_bounds_returns_none() {cell_key_out_of_bounds_returns_none849,30357
+    fn cell_key_includes_page_coords() {cell_key_includes_page_coords857,30629
+    fn cell_key_round_trips_through_model_evaluate() {cell_key_round_trips_through_model_evaluate873,31360
+    fn labels_join_with_slash_for_multi_cat_axis() {labels_join_with_slash_for_multi_cat_axis886,31846
+    fn row_count_excludes_group_headers() {row_count_excludes_group_headers901,32488
+    fn group_header_emitted_at_group_boundary() {group_header_emitted_at_group_boundary923,33250
+    fn collapsed_group_has_header_but_no_data_items() {collapsed_group_has_header_but_no_data_items952,34279
+    fn ungrouped_items_produce_no_headers() {ungrouped_items_produce_no_headers986,35569
+    fn cell_key_correct_with_grouped_items() {cell_key_correct_with_grouped_items1004,36074
+    fn data_row_to_visual_skips_headers() {data_row_to_visual_skips_headers1028,36941
+    fn data_col_to_visual_skips_headers() {data_col_to_visual_skips_headers1049,37814
+    fn row_group_for_finds_enclosing_group() {row_group_for_finds_enclosing_group1070,38651
+    fn row_group_for_returns_none_for_ungrouped() {row_group_for_returns_none_for_ungrouped1095,39465
+    fn col_group_for_finds_enclosing_group() {col_group_for_finds_enclosing_group1106,39910
+    fn col_group_for_returns_none_for_ungrouped() {col_group_for_returns_none_for_ungrouped1131,40741
+
+.claude/worktrees/improvise-ewi-formula-crate/crates/improvise-core/src/view/axis.rs,243
+pub enum Axis {Axis5,142
+    Row,Row6,158
+    Column,Column7,167
+    Page,Page8,179
+    None,None9,189
+impl std::fmt::Display for Axis {Axis12,202
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {fmt13,236
+
+.claude/worktrees/improvise-ewi-formula-crate/crates/improvise-core/src/view/mod.rs,77
+pub mod axis;axis1,0
+pub mod layout;layout2,14
+pub mod types;types3,30
+
+.claude/worktrees/improvise-ewi-formula-crate/examples/gen-grammar.rs,1493
+const GRAMMAR: &str = include_str!("../src/persistence/improv.pest");GRAMMAR24,822
+fn load_grammar() -> HashMap<String, (RuleType, Expr)> {load_grammar26,893
+const BARE_WORDS: &[&str] = &[BARE_WORDS38,1428
+const QUOTED_WORDS: &[&str] = &[QUOTED_WORDS69,1828
+const MODEL_NAMES: &[&str] = &[MODEL_NAMES84,2086
+const VIEW_NAMES: &[&str] = &["Default", "Summary", "Detail", "By Region", "Monthly"];VIEW_NAMES93,2250
+const FORMULA_EXPRS: &[&str] = &[FORMULA_EXPRS95,2338
+const FORMAT_STRINGS: &[&str] = &[",.0", ",.2f", ",.1f", ".0%"];FORMAT_STRINGS103,2529
+struct Xs64(u64);Xs64107,2816
+impl Xs64 {Xs64109,2835
+    fn new(seed: u64) -> Self {new110,2847
+    fn next(&mut self) -> u64 {next113,2911
+    fn byte(&mut self) -> u8 {byte119,3059
+    fn pick_from<'a>(&mut self, pool: &[&'a str]) -> &'a str {pick_from122,3131
+struct Gen<'g> {Gen129,3462
+    rules: &'g HashMap<String, (RuleType, Expr)>,rules130,3479
+    rng: Xs64,rng131,3529
+impl<'g> Gen<'g> {Gen134,3547
+    fn new(rules: &'g HashMap<String, (RuleType, Expr)>, seed: u64) -> Self {new135,3566
+    fn pick(&mut self) -> u8 {pick142,3729
+    fn try_override(&mut self, rule_name: &str, out: &mut String) -> bool {try_override147,3854
+    fn emit(&mut self, expr: &Expr, out: &mut String) {emit220,6431
+    fn generate(&mut self, rule_name: &str) -> Option<String> {generate290,9021
+fn print_rules(rules: &HashMap<String, (RuleType, Expr)>) {print_rules302,9403
+fn main() {main310,9596
+
+.claude/worktrees/improvise-ewi-formula-crate/examples/pretty-print.rs,23
+fn main() {main9,250
+
+.claude/worktrees/improvise-ewi-formula-crate/src/ui/formula_panel.rs,856
+pub struct FormulaContent<'a> {FormulaContent11,185
+    pub model: &'a Model,model12,217
+    pub mode: &'a AppMode,mode13,243
+impl<'a> FormulaContent<'a> {FormulaContent16,273
+    pub fn new(model: &'a Model, mode: &'a AppMode) -> Self {new17,303
+impl PanelContent for FormulaContent<'_> {FormulaContent22,403
+    fn is_active(&self, mode: &AppMode) -> bool {is_active23,446
+    fn active_color(&self) -> Color {active_color27,579
+    fn title(&self) -> &str {title31,646
+    fn item_count(&self) -> usize {item_count35,719
+    fn empty_message(&self) -> &str {empty_message39,798
+    fn render_item(&self, index: usize, is_selected: bool, inner: Rect, buf: &mut Buffer) {render_item43,888
+    fn footer_height(&self) -> u16 {footer_height62,1592
+    fn render_footer(&self, inner: Rect, buf: &mut Buffer) {render_footer70,1753
+
+.claude/worktrees/improvise-ewi-formula-crate/src/ui/view_panel.rs,823
+pub struct ViewContent<'a> {ViewContent12,214
+    view_names: Vec<String>,view_names13,243
+    active_view: String,active_view14,272
+    workbook: &'a Workbook,workbook15,297
+impl<'a> ViewContent<'a> {ViewContent18,328
+    pub fn new(workbook: &'a Workbook) -> Self {new19,355
+    fn axis_summary(&self, view_name: &str) -> String {axis_summary30,723
+impl PanelContent for ViewContent<'_> {ViewContent53,1553
+    fn is_active(&self, mode: &AppMode) -> bool {is_active54,1593
+    fn active_color(&self) -> Color {active_color58,1693
+    fn title(&self) -> &str {title62,1758
+    fn item_count(&self) -> usize {item_count66,1813
+    fn empty_message(&self) -> &str {empty_message70,1886
+    fn render_item(&self, index: usize, is_selected: bool, inner: Rect, buf: &mut Buffer) {render_item74,1952
+
+.claude/worktrees/improvise-ewi-formula-crate/src/ui/tile_bar.rs,462
+pub struct TileBar<'a> {TileBar13,237
+    pub model: &'a Model,model14,262
+    pub view: &'a View,view15,288
+    pub mode: &'a AppMode,mode16,312
+    pub tile_cat_idx: usize,tile_cat_idx17,339
+impl<'a> TileBar<'a> {TileBar20,371
+    pub fn new(new21,394
+    fn axis_display(axis: Axis) -> (&'static str, Color) {axis_display34,644
+impl<'a> Widget for TileBar<'a> {TileBar44,943
+    fn render(self, area: Rect, buf: &mut Buffer) {render45,977
+
+.claude/worktrees/improvise-ewi-formula-crate/src/ui/which_key.rs,358
+pub struct WhichKeyWidget<'a> {WhichKeyWidget10,241
+    hints: &'a [(String, &'static str)],hints11,273
+impl<'a> WhichKeyWidget<'a> {WhichKeyWidget14,317
+    pub fn new(hints: &'a [(String, &'static str)]) -> Self {new15,347
+impl Widget for WhichKeyWidget<'_> {WhichKeyWidget20,441
+    fn render(self, area: Rect, buf: &mut Buffer) {render21,478
+
+.claude/worktrees/improvise-ewi-formula-crate/src/ui/grid.rs,2938
+const MIN_COL_WIDTH: u16 = 5;MIN_COL_WIDTH14,347
+const MAX_COL_WIDTH: u16 = 32;MAX_COL_WIDTH15,377
+const MIN_ROW_HEADER_W: u16 = 4;MIN_ROW_HEADER_W16,408
+const MAX_ROW_HEADER_W: u16 = 24;MAX_ROW_HEADER_W17,441
+const ROW_HIGHLIGHT_BG: Color = Color::Indexed(237);ROW_HIGHLIGHT_BG19,556
+const GROUP_EXPANDED: &str = "▼";GROUP_EXPANDED20,609
+const GROUP_COLLAPSED: &str = "▶";GROUP_COLLAPSED21,645
+pub struct GridWidget<'a> {GridWidget23,683
+    pub model: &'a Model,model24,711
+    pub view: &'a View,view25,737
+    pub view_name: &'a str,view_name26,761
+    pub layout: &'a GridLayout,layout27,789
+    pub mode: &'a AppMode,mode28,821
+    pub search_query: &'a str,search_query29,848
+    pub buffers: &'a std::collections::HashMap<String, String>,buffers30,879
+    pub drill_state: Option<&'a crate::ui::app::DrillState>,drill_state31,943
+impl<'a> GridWidget<'a> {GridWidget34,1007
+    pub fn new(new36,1074
+    fn render_grid(&self, area: Rect, buf: &mut Buffer) {render_grid58,1601
+impl<'a> Widget for GridWidget<'a> {GridWidget502,19659
+    fn render(self, area: Rect, buf: &mut Buffer) {render503,19696
+pub fn compute_col_widths(compute_col_widths542,21058
+pub fn compute_row_header_width(layout: &GridLayout) -> u16 {compute_row_header_width601,23041
+pub fn compute_visible_cols(compute_visible_cols629,23918
+fn truncate(s: &str, max_width: usize) -> String {truncate655,24548
+mod tests {tests678,25137
+    fn render(wb: &mut Workbook, width: u16, height: u16) -> Buffer {render691,25714
+    fn buf_text(buf: &Buffer) -> String {buf_text714,26500
+    fn coord(pairs: &[(&str, &str)]) -> CellKey {coord729,26908
+    fn two_cat_model() -> Workbook {two_cat_model740,27246
+    fn column_headers_appear() {column_headers_appear765,28203
+    fn row_headers_appear() {row_headers_appear775,28689
+    fn cell_value_appears_in_correct_position() {cell_value_appears_in_correct_position785,29184
+    fn multiple_cell_values_all_appear() {multiple_cell_values_all_appear796,29546
+    fn unset_cells_show_no_value() {unset_cells_show_no_value817,30306
+    fn total_row_label_appears() {total_row_label_appears837,31279
+    fn total_row_sums_column_correctly() {total_row_sums_column_correctly844,31497
+    fn page_filter_bar_shows_category_and_selection() {page_filter_bar_shows_category_and_selection865,32285
+    fn page_filter_defaults_to_first_item() {page_filter_defaults_to_first_item889,33129
+    fn formula_cell_renders_computed_value() {formula_cell_renders_computed_value915,34123
+    fn two_row_categories_produce_cross_product_labels() {two_row_categories_produce_cross_product_labels947,35549
+    fn two_row_categories_include_all_coords_in_cell_lookup() {two_row_categories_include_all_coords_in_cell_lookup993,37388
+    fn two_column_categories_produce_cross_product_headers() {two_column_categories_produce_cross_product_headers1020,38388
+
+.claude/worktrees/improvise-ewi-formula-crate/src/ui/cat_tree.rs,1011
+pub enum CatTreeEntry {CatTreeEntry6,131
+    Category {Category8,212
+    Item { cat_name: String, item_name: String },Item14,341
+impl CatTreeEntry {CatTreeEntry17,394
+    pub fn cat_name(&self) -> &str {cat_name19,458
+pub fn build_cat_tree(model: &Model, expanded: &HashSet<String>) -> Vec<CatTreeEntry> {build_cat_tree28,713
+mod tests {tests52,1540
+    fn make_model_with_categories(cats: &[(&str, &[&str])]) -> Model {make_model_with_categories55,1571
+    fn empty_model_has_only_virtual_categories() {empty_model_has_only_virtual_categories68,1949
+    fn collapsed_category_shows_header_only() {collapsed_category_shows_header_only78,2332
+    fn expanded_category_shows_items() {expanded_category_shows_items94,2899
+    fn mixed_expanded_and_collapsed() {mixed_expanded_and_collapsed111,3622
+    fn cat_name_works_for_both_variants() {cat_name_works_for_both_variants133,4447
+    fn expanding_nonexistent_category_is_harmless() {expanding_nonexistent_category_is_harmless148,4883
+
+.claude/worktrees/improvise-ewi-formula-crate/src/ui/import_wizard_ui.rs,433
+pub struct ImportWizardWidget<'a> {ImportWizardWidget11,253
+    pub wizard: &'a ImportWizard,wizard12,289
+impl<'a> ImportWizardWidget<'a> {ImportWizardWidget15,326
+    pub fn new(wizard: &'a ImportWizard) -> Self {new16,360
+impl<'a> Widget for ImportWizardWidget<'a> {ImportWizardWidget21,444
+    fn render(self, area: Rect, buf: &mut Buffer) {render22,489
+fn truncate(s: &str, max: usize) -> String {truncate339,13390
+
+.claude/worktrees/improvise-ewi-formula-crate/src/ui/effect.rs,14177
+pub(crate) const RECORD_COORDS_CANNOT_BE_EMPTY: &str = "Record coordinates cannot be empty";RECORD_COORDS_CANNOT_BE_EMPTY9,160
+pub trait Effect: Debug {Effect13,358
+    fn apply(&self, app: &mut App);apply14,384
+    fn changes_mode(&self) -> bool {changes_mode16,470
+pub struct AddCategory(pub String);AddCategory24,749
+impl Effect for AddCategory {AddCategory25,785
+    fn apply(&self, app: &mut App) {apply26,815
+pub struct AddItem {AddItem32,930
+    pub category: String,category33,951
+    pub item: String,item34,977
+impl Effect for AddItem {AddItem36,1001
+    fn apply(&self, app: &mut App) {apply37,1027
+pub struct AddItemInGroup {AddItemInGroup47,1310
+    pub category: String,category48,1338
+    pub item: String,item49,1364
+    pub group: String,group50,1386
+impl Effect for AddItemInGroup {AddItemInGroup52,1411
+    fn apply(&self, app: &mut App) {apply53,1444
+pub struct SortData;SortData63,1749
+impl Effect for SortData {SortData64,1770
+    fn apply(&self, app: &mut App) {apply65,1797
+pub struct SetCell(pub CellKey, pub CellValue);SetCell71,1907
+impl Effect for SetCell {SetCell72,1955
+    fn apply(&self, app: &mut App) {apply73,1981
+pub struct ClearCell(pub CellKey);ClearCell79,2113
+impl Effect for ClearCell {ClearCell80,2148
+    fn apply(&self, app: &mut App) {apply81,2176
+pub struct AddFormula {AddFormula87,2287
+    pub raw: String,raw88,2311
+    pub target_category: String,target_category89,2332
+impl Effect for AddFormula {AddFormula91,2367
+    fn apply(&self, app: &mut App) {apply92,2396
+pub struct RemoveFormula {RemoveFormula113,3206
+    pub target: String,target114,3233
+    pub target_category: String,target_category115,3257
+impl Effect for RemoveFormula {RemoveFormula117,3292
+    fn apply(&self, app: &mut App) {apply118,3324
+pub struct EnterEditAtCursor;EnterEditAtCursor128,3619
+impl Effect for EnterEditAtCursor {EnterEditAtCursor129,3649
+    fn apply(&self, app: &mut App) {apply130,3685
+pub struct TogglePruneEmpty;TogglePruneEmpty148,4184
+impl Effect for TogglePruneEmpty {TogglePruneEmpty149,4213
+    fn apply(&self, app: &mut App) {apply150,4248
+pub struct ToggleCatExpand(pub String);ToggleCatExpand157,4399
+impl Effect for ToggleCatExpand {ToggleCatExpand158,4439
+    fn apply(&self, app: &mut App) {apply159,4473
+pub struct RemoveItem {RemoveItem167,4648
+    pub category: String,category168,4672
+    pub item: String,item169,4698
+impl Effect for RemoveItem {RemoveItem171,4722
+    fn apply(&self, app: &mut App) {apply172,4751
+pub struct RemoveCategory(pub String);RemoveCategory178,4882
+impl Effect for RemoveCategory {RemoveCategory179,4921
+    fn apply(&self, app: &mut App) {apply180,4954
+pub struct CreateView(pub String);CreateView188,5268
+impl Effect for CreateView {CreateView189,5303
+    fn apply(&self, app: &mut App) {apply190,5332
+pub struct DeleteView(pub String);DeleteView196,5438
+impl Effect for DeleteView {DeleteView197,5473
+    fn apply(&self, app: &mut App) {apply198,5502
+pub struct SwitchView(pub String);SwitchView204,5616
+impl Effect for SwitchView {SwitchView205,5651
+    fn apply(&self, app: &mut App) {apply206,5680
+pub struct ViewBack;ViewBack221,6153
+impl Effect for ViewBack {ViewBack222,6174
+    fn apply(&self, app: &mut App) {apply223,6201
+pub struct ViewForward;ViewForward238,6714
+impl Effect for ViewForward {ViewForward239,6738
+    fn apply(&self, app: &mut App) {apply240,6768
+pub struct SetAxis {SetAxis254,7201
+    pub category: String,category255,7222
+    pub axis: Axis,axis256,7248
+impl Effect for SetAxis {SetAxis258,7270
+    fn apply(&self, app: &mut App) {apply259,7296
+pub struct SetPageSelection {SetPageSelection267,7461
+    pub category: String,category268,7491
+    pub item: String,item269,7517
+impl Effect for SetPageSelection {SetPageSelection271,7541
+    fn apply(&self, app: &mut App) {apply272,7576
+pub struct ToggleGroup {ToggleGroup280,7752
+    pub category: String,category281,7777
+    pub group: String,group282,7803
+impl Effect for ToggleGroup {ToggleGroup284,7828
+    fn apply(&self, app: &mut App) {apply285,7858
+pub struct HideItem {HideItem293,8038
+    pub category: String,category294,8060
+    pub item: String,item295,8086
+impl Effect for HideItem {HideItem297,8110
+    fn apply(&self, app: &mut App) {apply298,8137
+pub struct ShowItem {ShowItem306,8304
+    pub category: String,category307,8326
+    pub item: String,item308,8352
+impl Effect for ShowItem {ShowItem310,8376
+    fn apply(&self, app: &mut App) {apply311,8403
+pub struct TransposeAxes;TransposeAxes319,8570
+impl Effect for TransposeAxes {TransposeAxes320,8596
+    fn apply(&self, app: &mut App) {apply321,8628
+pub struct CycleAxis(pub String);CycleAxis327,8748
+impl Effect for CycleAxis {CycleAxis328,8782
+    fn apply(&self, app: &mut App) {apply329,8810
+pub struct SetNumberFormat(pub String);SetNumberFormat335,8933
+impl Effect for SetNumberFormat {SetNumberFormat336,8973
+    fn apply(&self, app: &mut App) {apply337,9007
+pub struct SetSelected(pub usize, pub usize);SetSelected345,9353
+impl Effect for SetSelected {SetSelected346,9399
+    fn apply(&self, app: &mut App) {apply347,9429
+pub struct SetRowOffset(pub usize);SetRowOffset353,9560
+impl Effect for SetRowOffset {SetRowOffset354,9596
+    fn apply(&self, app: &mut App) {apply355,9627
+pub struct SetColOffset(pub usize);SetColOffset361,9750
+impl Effect for SetColOffset {SetColOffset362,9786
+    fn apply(&self, app: &mut App) {apply363,9817
+pub struct ChangeMode(pub AppMode);ChangeMode371,10154
+impl Effect for ChangeMode {ChangeMode372,10190
+    fn apply(&self, app: &mut App) {apply373,10219
+    fn changes_mode(&self) -> bool {changes_mode376,10297
+pub struct SetStatus(pub String);SetStatus382,10373
+impl Effect for SetStatus {SetStatus383,10407
+    fn apply(&self, app: &mut App) {apply384,10435
+pub struct MarkDirty;MarkDirty390,10539
+impl Effect for MarkDirty {MarkDirty391,10561
+    fn apply(&self, app: &mut App) {apply392,10589
+pub struct SetYanked(pub Option<CellValue>);SetYanked398,10678
+impl Effect for SetYanked {SetYanked399,10723
+    fn apply(&self, app: &mut App) {apply400,10751
+pub struct SetSearchQuery(pub String);SetSearchQuery406,10851
+impl Effect for SetSearchQuery {SetSearchQuery407,10890
+    fn apply(&self, app: &mut App) {apply408,10923
+pub struct SetSearchMode(pub bool);SetSearchMode414,11029
+impl Effect for SetSearchMode {SetSearchMode415,11065
+    fn apply(&self, app: &mut App) {apply416,11097
+pub struct SetBuffer {SetBuffer423,11229
+    pub name: String,name424,11252
+    pub value: String,value425,11274
+impl Effect for SetBuffer {SetBuffer427,11299
+    fn apply(&self, app: &mut App) {apply428,11327
+pub struct SetTileCatIdx(pub usize);SetTileCatIdx439,11655
+impl Effect for SetTileCatIdx {SetTileCatIdx440,11692
+    fn apply(&self, app: &mut App) {apply441,11724
+pub struct StartDrill(pub Vec<(CellKey, CellValue)>);StartDrill449,11923
+impl Effect for StartDrill {StartDrill450,11977
+    fn apply(&self, app: &mut App) {apply451,12006
+pub struct ApplyAndClearDrill;ApplyAndClearDrill461,12321
+impl Effect for ApplyAndClearDrill {ApplyAndClearDrill462,12352
+    fn apply(&self, app: &mut App) {apply463,12389
+pub struct SetDrillPendingEdit {SetDrillPendingEdit523,14762
+    pub record_idx: usize,record_idx524,14795
+    pub col_name: String,col_name525,14822
+    pub new_value: String,new_value526,14848
+impl Effect for SetDrillPendingEdit {SetDrillPendingEdit528,14877
+    fn apply(&self, app: &mut App) {apply529,14915
+pub struct Save;Save542,15401
+impl Effect for Save {Save543,15418
+    fn apply(&self, app: &mut App) {apply544,15441
+pub struct SaveAs(pub PathBuf);SaveAs562,16006
+impl Effect for SaveAs {SaveAs563,16038
+    fn apply(&self, app: &mut App) {apply564,16063
+pub struct WizardKey {WizardKey582,16679
+    pub key_code: crossterm::event::KeyCode,key_code583,16702
+impl Effect for WizardKey {WizardKey585,16749
+    fn apply(&self, app: &mut App) {apply586,16777
+pub struct StartImportWizard(pub String);StartImportWizard712,22429
+impl Effect for StartImportWizard {StartImportWizard713,22471
+    fn apply(&self, app: &mut App) {apply714,22507
+pub struct ExportCsv(pub PathBuf);ExportCsv733,23142
+impl Effect for ExportCsv {ExportCsv734,23177
+    fn apply(&self, app: &mut App) {apply735,23205
+pub struct LoadModel(pub PathBuf);LoadModel750,23693
+impl Effect for LoadModel {LoadModel751,23728
+    fn apply(&self, app: &mut App) {apply752,23756
+pub struct ImportJsonHeadless {ImportJsonHeadless768,24268
+    pub path: PathBuf,path769,24300
+    pub model_name: Option<String>,model_name770,24323
+    pub array_path: Option<String>,array_path771,24359
+impl Effect for ImportJsonHeadless {ImportJsonHeadless773,24397
+    fn apply(&self, app: &mut App) {apply774,24434
+pub struct SetPanelOpen {SetPanelOpen878,28005
+    pub panel: Panel,panel879,28031
+    pub open: bool,open880,28053
+pub enum Panel {Panel884,28106
+    Formula,Formula885,28123
+    Category,Category886,28136
+    View,View887,28150
+impl Panel {Panel890,28163
+    pub fn mode(self) -> AppMode {mode891,28176
+impl Effect for SetPanelOpen {SetPanelOpen900,28406
+    fn apply(&self, app: &mut App) {apply901,28437
+pub struct SetPanelCursor {SetPanelCursor911,28731
+    pub panel: Panel,panel912,28759
+    pub cursor: usize,cursor913,28781
+impl Effect for SetPanelCursor {SetPanelCursor915,28806
+    fn apply(&self, app: &mut App) {apply916,28839
+pub fn mark_dirty() -> Box<dyn Effect> {mark_dirty927,29301
+pub fn set_status(msg: impl Into<String>) -> Box<dyn Effect> {set_status931,29369
+pub fn change_mode(mode: AppMode) -> Box<dyn Effect> {change_mode935,29471
+pub fn set_selected(row: usize, col: usize) -> Box<dyn Effect> {set_selected939,29560
+pub struct HelpPageNext;HelpPageNext946,29870
+impl Effect for HelpPageNext {HelpPageNext947,29895
+    fn apply(&self, app: &mut App) {apply948,29926
+pub struct HelpPagePrev;HelpPagePrev955,30125
+impl Effect for HelpPagePrev {HelpPagePrev956,30150
+    fn apply(&self, app: &mut App) {apply957,30181
+pub struct HelpPageSet(pub usize);HelpPageSet963,30301
+impl Effect for HelpPageSet {HelpPageSet964,30336
+    fn apply(&self, app: &mut App) {apply965,30366
+pub fn help_page_next() -> Box<dyn Effect> {help_page_next970,30444
+pub fn help_page_prev() -> Box<dyn Effect> {help_page_prev974,30519
+pub fn help_page_set(page: usize) -> Box<dyn Effect> {help_page_set978,30594
+mod tests {tests983,30697
+    fn test_app() -> App {test_app988,30813
+    fn add_category_effect() {add_category_effect1002,31444
+    fn add_item_to_existing_category() {add_item_to_existing_category1009,31653
+    fn add_item_to_nonexistent_category_sets_status() {add_item_to_nonexistent_category_sets_status1028,32148
+    fn set_cell_and_clear_cell() {set_cell_and_clear_cell1039,32457
+    fn add_formula_valid() {add_formula_valid1053,32950
+    fn add_formula_adds_target_item_to_category() {add_formula_adds_target_item_to_category1067,33491
+    fn add_formula_to_measure_shows_in_effective_items() {add_formula_to_measure_shows_in_effective_items1100,34562
+    fn add_formula_invalid_sets_error_status() {add_formula_invalid_sets_error_status1126,35512
+    fn remove_formula_effect() {remove_formula_effect1137,35829
+    fn switch_view_pushes_to_back_stack() {switch_view_pushes_to_back_stack1156,36530
+    fn switch_view_to_same_does_not_push_stack() {switch_view_to_same_does_not_push_stack1170,37054
+    fn view_back_and_forward() {view_back_and_forward1177,37266
+    fn view_back_with_empty_stack_is_noop() {view_back_with_empty_stack_is_noop1199,38100
+    fn create_and_delete_view() {create_and_delete_view1207,38342
+    fn set_axis_effect() {set_axis_effect1217,38667
+    fn transpose_axes_effect() {transpose_axes_effect1228,38949
+    fn set_selected_effect() {set_selected_effect1266,40173
+    fn set_row_and_col_offset() {set_row_and_col_offset1273,40365
+    fn change_mode_effect() {change_mode_effect1284,40835
+    fn set_buffer_empty_clears() {set_buffer_empty_clears1294,41199
+    fn set_status_effect() {set_status_effect1307,41585
+    fn mark_dirty_effect() {mark_dirty_effect1314,41768
+    fn set_yanked_effect() {set_yanked_effect1322,41942
+    fn set_search_query_and_mode() {set_search_query_and_mode1329,42153
+    fn set_buffer_normal_key() {set_buffer_normal_key1342,42664
+    fn set_buffer_search_writes_to_search_query() {set_buffer_search_writes_to_search_query1353,42947
+    fn set_panel_open_and_cursor() {set_panel_open_and_cursor1367,43487
+    fn set_tile_cat_idx_effect() {set_tile_cat_idx_effect1399,44215
+    fn help_page_navigation() {help_page_navigation1408,44561
+    fn help_page_prev_clamps_at_zero() {help_page_prev_clamps_at_zero1422,44990
+    fn start_drill_and_apply_clear_drill_with_no_edits() {start_drill_and_apply_clear_drill_with_no_edits1431,45343
+    fn apply_and_clear_drill_with_value_edit() {apply_and_clear_drill_with_value_edit1448,45952
+    fn apply_and_clear_drill_with_coord_rename() {apply_and_clear_drill_with_coord_rename1475,46849
+    fn apply_and_clear_drill_empty_value_clears_cell() {apply_and_clear_drill_empty_value_clears_cell1517,48255
+    fn toggle_prune_empty_effect() {toggle_prune_empty_effect1543,49214
+    fn toggle_cat_expand_effect() {toggle_cat_expand_effect1553,49585
+    fn remove_item_and_category() {remove_item_and_category1563,49957
+    fn set_number_format_effect() {set_number_format_effect1587,50747
+    fn set_page_selection_effect() {set_page_selection_effect1596,51148
+    fn hide_and_show_item_effects() {hide_and_show_item_effects1609,51645
+    fn toggle_group_effect() {toggle_group_effect1629,52335
+    fn cycle_axis_effect() {cycle_axis_effect1656,53171
+    fn save_without_file_path_shows_status() {save_without_file_path_shows_status1667,53637
+    fn panel_mode_mapping() {panel_mode_mapping1676,54000
+
+.claude/worktrees/improvise-ewi-formula-crate/src/ui/mod.rs,400
+pub mod app;app1,0
+pub mod cat_tree;cat_tree2,13
+pub mod category_panel;category_panel3,31
+pub mod effect;effect4,55
+pub mod formula_panel;formula_panel5,71
+pub mod grid;grid6,94
+pub mod help;help7,108
+pub mod import_wizard_ui;import_wizard_ui8,122
+pub mod panel;panel9,148
+pub mod tile_bar;tile_bar10,163
+pub mod view_panel;view_panel11,181
+pub mod which_key;which_key12,201
+
+.claude/worktrees/improvise-ewi-formula-crate/src/ui/category_panel.rs,749
+fn axis_display(axis: Axis) -> (&'static str, Color) {axis_display13,273
+pub struct CategoryContent<'a> {CategoryContent22,557
+    view: &'a View,view23,590
+    tree: Vec<CatTreeEntry>,tree24,610
+impl<'a> CategoryContent<'a> {CategoryContent27,642
+    pub fn new(new28,673
+impl PanelContent for CategoryContent<'_> {CategoryContent38,901
+    fn is_active(&self, mode: &AppMode) -> bool {is_active39,945
+    fn active_color(&self) -> Color {active_color46,1139
+    fn title(&self) -> &str {title50,1204
+    fn item_count(&self) -> usize {item_count54,1264
+    fn empty_message(&self) -> &str {empty_message58,1331
+    fn render_item(&self, index: usize, is_selected: bool, inner: Rect, buf: &mut Buffer) {render_item62,1426
+
+.claude/worktrees/improvise-ewi-formula-crate/src/ui/help.rs,2063
+pub const HELP_PAGE_COUNT: usize = 5;HELP_PAGE_COUNT9,176
+struct HelpStyles {HelpStyles12,261
+    heading: Style,heading13,281
+    key: Style,key14,301
+    dim: Style,dim15,317
+    normal: Style,normal16,333
+    accent: Style,accent17,352
+    banner: Style,banner18,371
+impl HelpStyles {HelpStyles21,393
+    fn new() -> Self {new22,411
+struct HelpLine {HelpLine40,1046
+    key: &'static str,key41,1064
+    desc: &'static str,desc42,1087
+    style: Style,style43,1111
+impl HelpLine {HelpLine46,1132
+    fn heading(text: &'static str, styles: &HelpStyles) -> Self {heading47,1148
+    fn key(k: &'static str, d: &'static str, styles: &HelpStyles) -> Self {key55,1326
+    fn dim(k: &'static str, d: &'static str, styles: &HelpStyles) -> Self {dim63,1506
+    fn text(t: &'static str, styles: &HelpStyles) -> Self {text71,1686
+    fn accent(t: &'static str, styles: &HelpStyles) -> Self {accent79,1854
+    fn banner(t: &'static str, styles: &HelpStyles) -> Self {banner87,2024
+    fn blank() -> Self {blank95,2194
+const PAGE_TITLES: [&str; HELP_PAGE_COUNT] = [PAGE_TITLES105,2375
+fn page_welcome(s: &HelpStyles) -> Vec<HelpLine> {page_welcome115,2699
+fn page_navigation(s: &HelpStyles) -> Vec<HelpLine> {page_navigation188,5348
+fn page_editing(s: &HelpStyles) -> Vec<HelpLine> {page_editing235,7304
+fn page_panels(s: &HelpStyles) -> Vec<HelpLine> {page_panels292,9521
+fn page_commands(s: &HelpStyles) -> Vec<HelpLine> {page_commands341,11854
+fn page_content(page: usize) -> Vec<HelpLine> {page_content393,14082
+pub struct HelpWidget {HelpWidget407,14628
+    page: usize,page408,14652
+impl HelpWidget {HelpWidget411,14672
+    pub fn new(page: usize) -> Self {new412,14690
+impl Widget for HelpWidget {HelpWidget419,14841
+    fn render(self, area: Rect, buf: &mut Buffer) {render420,14870
+fn render_tab_bar(buf: &mut Buffer, x: u16, y: u16, width: u16, active_page: usize) {render_tab_bar500,18484
+fn render_footer(buf: &mut Buffer, x: u16, y: u16, width: u16, page: usize) {render_footer542,19631
+
+.claude/worktrees/improvise-ewi-formula-crate/src/ui/panel.rs,999
+pub trait PanelContent {PanelContent11,234
+    fn is_active(&self, mode: &AppMode) -> bool;is_active13,330
+    fn active_color(&self) -> Color;active_color15,460
+    fn title(&self) -> &str;title17,566
+    fn item_count(&self) -> usize;item_count19,630
+    fn empty_message(&self) -> &str;empty_message21,718
+    fn render_item(&self, index: usize, is_selected: bool, inner: Rect, buf: &mut Buffer);render_item24,892
+    fn footer_height(&self) -> u16 {footer_height26,1052
+    fn render_footer(&self, _inner: Rect, _buf: &mut Buffer) {}render_footer30,1175
+pub struct Panel<'a, C: PanelContent> {Panel34,1331
+    content: C,content35,1371
+    mode: &'a AppMode,mode36,1387
+    cursor: usize,cursor37,1410
+impl<'a, C: PanelContent> Panel<'a, C> {Panel40,1432
+    pub fn new(content: C, mode: &'a AppMode, cursor: usize) -> Self {new41,1473
+impl<C: PanelContent> Widget for Panel<'_, C> {Panel50,1637
+    fn render(self, area: Rect, buf: &mut Buffer) {render51,1685
+
+.claude/worktrees/improvise-ewi-formula-crate/src/ui/app.rs,7971
+pub struct ViewFrame {ViewFrame25,689
+    pub view_name: String,view_name26,712
+    pub mode: AppMode,mode27,739
+pub struct DrillState {DrillState33,908
+    pub records: Rc<Vec<(crate::model::cell::CellKey, crate::model::cell::CellValue)>>,records35,1015
+    pub pending_edits: std::collections::HashMap<(usize, String), String>,pending_edits38,1240
+pub struct MinibufferConfig {MinibufferConfig44,1469
+    pub buffer_key: &'static str,buffer_key45,1499
+    pub prompt: String,prompt46,1533
+    pub color: Color,color47,1557
+pub enum AppMode {AppMode51,1617
+    Normal,Normal52,1636
+    Editing {Editing53,1648
+    FormulaEdit {FormulaEdit56,1704
+    FormulaPanel,FormulaPanel59,1764
+    CategoryPanel,CategoryPanel60,1782
+    CategoryAdd {CategoryAdd62,1874
+    ItemAdd {ItemAdd66,2012
+    ViewPanel,ViewPanel70,2094
+    TileSelect,TileSelect71,2109
+    ImportWizard,ImportWizard72,2125
+    ExportPrompt {ExportPrompt73,2143
+    CommandMode {CommandMode77,2239
+    Help,Help80,2299
+    Quit,Quit81,2309
+    RecordsNormal,RecordsNormal83,2401
+    RecordsEditing {RecordsEditing85,2503
+impl AppMode {AppMode90,2569
+    pub fn minibuffer(&self) -> Option<&MinibufferConfig> {minibuffer92,2657
+    pub fn is_editing(&self) -> bool {is_editing106,3186
+    pub fn editing() -> Self {editing110,3307
+    pub fn records_editing() -> Self {records_editing120,3548
+    pub fn is_records(&self) -> bool {is_records131,3859
+    pub fn formula_edit() -> Self {formula_edit135,3979
+    pub fn command_mode() -> Self {command_mode145,4234
+    pub fn category_add() -> Self {category_add155,4483
+    pub fn item_add(category: String) -> Self {item_add165,4746
+    pub fn export_prompt() -> Self {export_prompt177,5067
+pub struct App {App188,5331
+    pub workbook: Workbook,workbook189,5348
+    pub file_path: Option<PathBuf>,file_path190,5376
+    pub mode: AppMode,mode191,5412
+    pub status_msg: String,status_msg192,5435
+    pub wizard: Option<ImportWizard>,wizard193,5463
+    pub last_autosave: Instant,last_autosave194,5501
+    pub search_query: String,search_query195,5533
+    pub search_mode: bool,search_mode196,5563
+    pub formula_panel_open: bool,formula_panel_open197,5590
+    pub category_panel_open: bool,category_panel_open198,5624
+    pub view_panel_open: bool,view_panel_open199,5659
+    pub cat_panel_cursor: usize,cat_panel_cursor200,5690
+    pub view_panel_cursor: usize,view_panel_cursor201,5723
+    pub formula_cursor: usize,formula_cursor202,5757
+    pub dirty: bool,dirty203,5788
+    pub yanked: Option<CellValue>,yanked205,5849
+    pub tile_cat_idx: usize,tile_cat_idx207,5949
+    pub view_back_stack: Vec<ViewFrame>,view_back_stack210,6101
+    pub view_forward_stack: Vec<ViewFrame>,view_forward_stack212,6222
+    pub drill_state: Option<DrillState>,drill_state217,6547
+    pub help_page: usize,help_page219,6645
+    pub term_width: u16,term_width221,6735
+    pub term_height: u16,term_height222,6760
+    pub expanded_cats: std::collections::HashSet<String>,expanded_cats224,6847
+    pub buffers: HashMap<String, String>,buffers226,6953
+    pub transient_keymap: Option<Arc<Keymap>>,transient_keymap228,7080
+    pub layout: GridLayout,layout231,7257
+    keymap_set: KeymapSet,keymap_set232,7285
+impl App {App235,7315
+    pub fn new(mut workbook: Workbook, file_path: Option<PathBuf>) -> Self {new236,7326
+    pub fn rebuild_layout(&mut self) {rebuild_layout281,9201
+    pub fn cmd_context(&self, key: KeyCode, _mods: KeyModifiers) -> CmdContext<'_> {cmd_context289,9590
+    pub fn apply_effects(&mut self, effects: Vec<Box<dyn super::effect::Effect>>) {apply_effects355,12403
+    pub fn is_empty_model(&self) -> bool {is_empty_model364,12767
+    pub fn handle_key(&mut self, key: KeyEvent) -> Result<()> {handle_key376,13137
+    pub fn autosave_if_needed(&mut self) {autosave_if_needed404,14152
+    pub fn start_import_wizard(&mut self, json: serde_json::Value) {start_import_wizard415,14534
+    pub fn hint_text(&self) -> &'static str {hint_text421,14763
+mod tests {tests456,16371
+    fn two_col_model() -> App {two_col_model459,16402
+    fn run_cmd(app: &mut App, cmd: &dyn crate::command::cmd::Cmd) {run_cmd471,16931
+    fn enter_advance_cmd(app: &App) -> crate::command::cmd::navigation::EnterAdvance {enter_advance_cmd478,17172
+    fn enter_advance_moves_down_within_column() {enter_advance_moves_down_within_column495,17727
+    fn enter_advance_wraps_to_top_of_next_column() {enter_advance_wraps_to_top_of_next_column504,18034
+    fn enter_advance_stays_at_bottom_right() {enter_advance_stays_at_bottom_right514,18408
+    fn import_command_switches_to_import_wizard_mode() {import_command_switches_to_import_wizard_mode523,18712
+    fn execute_import_command_leaves_mode_as_import_wizard() {execute_import_command_leaves_mode_as_import_wizard536,19247
+    fn command_mode_typing_appends_to_buffer() {command_mode_typing_appends_to_buffer548,19721
+    fn col_offset_scrolls_when_cursor_moves_past_visible_columns() {col_offset_scrolls_when_cursor_moves_past_visible_columns564,20353
+    fn home_jumps_to_first_col() {home_jumps_to_first_col611,22409
+    fn end_jumps_to_last_col() {end_jumps_to_last_col620,22721
+    fn page_down_scrolls_by_three_quarters_visible() {page_down_scrolls_by_three_quarters_visible629,23030
+    fn page_up_scrolls_backward() {page_up_scrolls_backward653,23883
+    fn jump_last_row_scrolls_with_small_terminal() {jump_last_row_scrolls_with_small_terminal670,24416
+    fn ctrl_d_scrolls_viewport_with_small_terminal() {ctrl_d_scrolls_viewport_with_small_terminal697,25495
+    fn tab_in_edit_mode_commits_and_moves_right() {tab_in_edit_mode_commits_and_moves_right725,26631
+    fn entering_records_mode_sorts_existing_data() {entering_records_mode_sorts_existing_data755,27866
+    fn add_record_row_in_empty_records_view_creates_first_row() {add_record_row_in_empty_records_view_creates_first_row791,29341
+    fn edit_record_row_in_blank_model_persists_value() {edit_record_row_in_blank_model_persists_value822,30595
+    fn records_model_with_two_rows() -> App {records_model_with_two_rows861,32268
+    fn tab_on_bottom_right_of_records_inserts_below() {tab_on_bottom_right_of_records_inserts_below916,34254
+    fn drill_edit_is_staged_until_view_back() {drill_edit_is_staged_until_view_back954,35691
+    fn blanking_records_category_does_not_create_empty_item() {blanking_records_category_does_not_create_empty_item1013,38153
+    fn command_mode_buffer_cleared_on_reentry() {command_mode_buffer_cleared_on_reentry1043,39263
+    fn fresh_model_is_empty() {fresh_model_is_empty1064,40264
+    fn model_with_user_category_is_not_empty() {model_with_user_category_is_not_empty1073,40508
+    fn help_page_next_advances_page() {help_page_next_advances_page1086,41001
+    fn help_page_prev_goes_back() {help_page_prev_goes_back1097,41349
+    fn help_page_clamps_at_zero() {help_page_clamps_at_zero1108,41693
+    fn help_page_clamps_at_max() {help_page_clamps_at_max1119,42037
+    fn help_q_returns_to_normal() {help_q_returns_to_normal1138,42698
+    fn help_esc_returns_to_normal() {help_esc_returns_to_normal1151,43069
+    fn help_colon_enters_command_mode() {help_colon_enters_command_mode1164,43438
+    fn add_item_to_nonexistent_category_sets_status() {add_item_to_nonexistent_category_sets_status1180,44039
+    fn add_formula_with_bad_syntax_sets_status() {add_formula_with_bad_syntax_sets_status1196,44557
+    fn tile_axis_change_stays_in_tile_select() {tile_axis_change_stays_in_tile_select1214,45231
+    fn category_panel_colon_enters_command_mode() {category_panel_colon_enters_command_mode1236,46048
+    fn view_panel_colon_enters_command_mode() {view_panel_colon_enters_command_mode1250,46486
+    fn tile_select_colon_enters_command_mode() {tile_select_colon_enters_command_mode1264,46912
+
+.claude/worktrees/improvise-ewi-formula-crate/src/lib.rs,142
+pub mod command;command1,0
+pub mod draw;draw2,17
+pub mod import;import5,101
+pub mod persistence;persistence7,148
+pub mod ui;ui8,169
+
+.claude/worktrees/improvise-ewi-formula-crate/src/draw.rs,1275
+struct TuiContext<'a> {TuiContext30,856
+    terminal: Terminal<CrosstermBackend<&'a mut Stdout>>,terminal31,880
+impl<'a> TuiContext<'a> {TuiContext34,941
+    fn enter(out: &'a mut Stdout) -> Result<Self> {enter35,967
+impl<'a> Drop for TuiContext<'a> {TuiContext45,1231
+    fn drop(&mut self) {drop46,1266
+pub fn run_tui(run_tui52,1413
+fn fill_line(left: String, right: &str, width: u16) -> String {fill_line95,2657
+fn centered_popup(area: Rect, width: u16, height: u16) -> Rect {centered_popup100,2843
+fn draw_popup_frame(f: &mut Frame, popup: Rect, title: &str, border_color: Color) -> Rect {draw_popup_frame108,3120
+fn mode_name(mode: &AppMode) -> &'static str {mode_name119,3475
+fn mode_style(mode: &AppMode) -> Style {mode_style140,4269
+fn draw(f: &mut Frame, app: &App) {draw151,4724
+fn draw_title(f: &mut Frame, area: Rect, app: &App) {draw_title190,6065
+fn draw_content(f: &mut Frame, area: Rect, app: &App) {draw_content216,6802
+fn draw_tile_bar(f: &mut Frame, area: Rect, app: &App) {draw_tile_bar278,8826
+fn draw_bottom_bar(f: &mut Frame, area: Rect, app: &App) {draw_bottom_bar290,9087
+fn draw_status(f: &mut Frame, area: Rect, app: &App) {draw_status312,9712
+fn draw_welcome(f: &mut Frame, area: Rect) {draw_welcome334,10396
+
+.claude/worktrees/improvise-ewi-formula-crate/src/persistence/mod.rs,4162
+struct ImprovParser;ImprovParser18,472
+fn is_bare_name(name: &str) -> bool {is_bare_name23,713
+fn escape_pipe(s: &str) -> String {escape_pipe33,1046
+    fn text_value_is_empty_string() {text_value_is_empty_string1335,48029
+    fn text_value_with_newline() {text_value_with_newline1353,48583
+    fn text_value_looks_like_number() {text_value_looks_like_number1371,49161
+    fn text_value_with_hash_prefix() {text_value_with_hash_prefix1389,49739
+    fn item_name_with_brackets_misinterpreted_as_group() {item_name_with_brackets_misinterpreted_as_group1407,50327
+    fn category_name_with_comma_space_in_data() {category_name_with_comma_space_in_data1429,51145
+    fn item_name_with_equals_sign_in_data() {item_name_with_equals_sign_in_data1455,52015
+    fn view_name_with_parentheses() {view_name_with_parentheses1478,52798
+    fn multiple_tricky_text_cells() {multiple_tricky_text_cells1494,53324
+mod parser_prop_tests {parser_prop_tests1534,54758
+    fn coord(pairs: &[(&str, &str)]) -> CellKey {coord1540,54936
+    fn safe_ident() -> impl Strategy<Value = String> {safe_ident1550,55217
+    fn tricky_text() -> impl Strategy<Value = String> {tricky_text1555,55375
+    fn cell_value() -> impl Strategy<Value = CellValue> {cell_value1572,55928
+    fn arbitrary_model() -> impl Strategy<Value = Workbook> {arbitrary_model1582,56288
+mod parser_edge_cases {parser_edge_cases1803,65284
+    fn coord(pairs: &[(&str, &str)]) -> CellKey {coord1809,65471
+    fn backtick_in_category_name() {backtick_in_category_name1821,65851
+    fn item_name_with_both_equals_and_comma() {item_name_with_both_equals_and_comma1844,66611
+    fn hidden_item_with_slash_in_name() {hidden_item_with_slash_in_name1869,67555
+    fn collapsed_group_with_slash_in_name() {collapsed_group_with_slash_in_name1886,68182
+    fn view_name_ending_with_active_string() {view_name_ending_with_active_string1900,68649
+    fn group_name_with_brackets() {group_name_with_brackets1919,69439
+    fn item_in_group_where_item_has_brackets() {item_in_group_where_item_has_brackets1944,70296
+    fn parse_empty_string() {parse_empty_string1972,71388
+    fn parse_just_model_name() {parse_just_model_name1981,71639
+    fn parse_data_without_value() {parse_data_without_value1987,71799
+    fn parse_data_with_empty_coords() {parse_data_with_empty_coords1994,72031
+    fn parse_duplicate_categories() {parse_duplicate_categories2001,72265
+    fn parse_category_with_no_items() {parse_category_with_no_items2010,72640
+    fn number_negative_zero_roundtrips() {number_negative_zero_roundtrips2021,73176
+    fn number_very_large_roundtrips() {number_very_large_roundtrips2040,73894
+    fn number_very_small_roundtrips() {number_very_small_roundtrips2062,74645
+    fn number_pi_roundtrips() {number_pi_roundtrips2084,75402
+    fn model_name_with_leading_trailing_spaces() {model_name_with_leading_trailing_spaces2108,76302
+    fn category_name_with_trailing_spaces() {category_name_with_trailing_spaces2116,76599
+    fn data_line_with_extra_whitespace() {data_line_with_extra_whitespace2124,76913
+    fn three_categories_round_trip() {three_categories_round_trip2134,77453
+    fn text_value_with_backslash() {text_value_with_backslash2164,78624
+    fn text_value_with_backslash_n_literal() {text_value_with_backslash_n_literal2185,79361
+mod generator {generator2213,80532
+    fn load_grammar() -> HashMap<String, (RuleType, Expr)> {load_grammar2220,80746
+    struct Gen<'g> {Gen2236,81471
+        rules: &'g HashMap<String, (RuleType, Expr)>,rules2237,81492
+        choices: Vec<u8>,choices2238,81546
+        pos: usize,pos2239,81572
+    impl<'g> Gen<'g> {Gen2242,81599
+        fn new(rules: &'g HashMap<String, (RuleType, Expr)>, choices: Vec<u8>) -> Self {new2243,81622
+        fn pick(&mut self) -> u8 {pick2252,81885
+        fn emit(&mut self, expr: &Expr, out: &mut String) {emit2258,82042
+        fn generate(&mut self, rule_name: &str) -> String {generate2334,85100
+    pub fn improv_file() -> impl Strategy<Value = String> {improv_file2344,85451
+mod grammar_prop_tests {grammar_prop_tests2355,85811
+
+.claude/worktrees/improvise-ewi-formula-crate/src/main.rs,2931
+fn main() -> Result<()> {main20,386
+struct Cli {Cli28,623
+    file: Option<PathBuf>,file30,673
+    command: Option<Commands>,command33,728
+trait Runnable {Runnable37,779
+    fn run(self, model_file: Option<PathBuf>) -> Result<()>;run38,796
+enum Commands {Commands43,909
+    Import(ImportArgs),Import45,996
+    Cmd(CmdArgs),Cmd47,1069
+    Script(ScriptArgs),Script49,1136
+    Open(OpenTui),Open51,1216
+struct ImportArgs {ImportArgs55,1260
+    files: Vec<PathBuf>,files57,1349
+    category: Vec<String>,category61,1446
+    measure: Vec<String>,measure65,1542
+    time: Vec<String>,time69,1640
+    skip: Vec<String>,skip73,1735
+    extract: Vec<String>,extract77,1839
+    axis: Vec<String>,axis81,1940
+    formula: Vec<String>,formula85,2046
+    name: Option<String>,name89,2137
+    no_wizard: bool,no_wizard93,2217
+    output: Option<PathBuf>,output97,2307
+struct CmdArgs {CmdArgs101,2361
+    json: Vec<String>,json103,2407
+    file: Option<PathBuf>,file107,2487
+struct ScriptArgs {ScriptArgs111,2539
+    path: PathBuf,path113,2619
+    file: Option<PathBuf>,file117,2695
+struct OpenTui;OpenTui121,2747
+impl Runnable for OpenTui {OpenTui122,2763
+    fn run(self, model_file: Option<PathBuf>) -> Result<()> {run123,2791
+impl Runnable for ImportArgs {ImportArgs129,2965
+    fn run(self, model_file: Option<PathBuf>) -> Result<()> {run130,2996
+impl Runnable for CmdArgs {CmdArgs156,3875
+    fn run(self, _model_file: Option<PathBuf>) -> Result<()> {run157,3903
+impl Runnable for ScriptArgs {ScriptArgs162,4029
+    fn run(self, _model_file: Option<PathBuf>) -> Result<()> {run163,4060
+struct ImportConfig {ImportConfig170,4390
+    categories: Vec<String>,categories171,4412
+    measures: Vec<String>,measures172,4441
+    time_fields: Vec<String>,time_fields173,4468
+    skip_fields: Vec<String>,skip_fields174,4498
+    extractions: Vec<(String, String)>,extractions175,4528
+    axes: Vec<(String, String)>,axes176,4568
+    formulas: Vec<String>,formulas177,4601
+    name: Option<String>,name178,4628
+fn parse_colon_pairs(args: &[String]) -> Vec<(String, String)> {parse_colon_pairs181,4657
+fn apply_config_to_pipeline(pipeline: &mut import::wizard::ImportPipeline, config: &ImportConfigapply_config_to_pipeline190,4891
+fn apply_axis_overrides(wb: &mut Workbook, axes: &[(String, String)]) {apply_axis_overrides233,6326
+fn run_headless_import(run_headless_import248,6776
+fn run_wizard_import(run_wizard_import269,7439
+fn get_import_data(paths: &[PathBuf]) -> Option<Value> {get_import_data283,8051
+fn run_headless_commands(cmds: &[String], file: &Option<PathBuf>) -> Result<()> {run_headless_commands330,9706
+fn run_headless_script(script_path: &PathBuf, file: &Option<PathBuf>) -> Result<()> {run_headless_script362,10642
+fn get_initial_workbook(file_path: &Option<PathBuf>) -> Result<Workbook> {get_initial_workbook370,11120
+
+.claude/worktrees/improvise-ewi-formula-crate/src/command/cmd/core.rs,4962
+pub struct CmdContext<'a> {CmdContext18,571
+    pub model: &'a Model,model19,599
+    pub workbook: &'a Workbook,workbook20,625
+    pub view: &'a View,view21,657
+    pub layout: &'a GridLayout,layout22,681
+    pub registry: &'a CmdRegistry,registry23,713
+    pub mode: &'a AppMode,mode24,748
+    pub selected: (usize, usize),selected25,775
+    pub row_offset: usize,row_offset26,809
+    pub col_offset: usize,col_offset27,836
+    pub search_query: &'a str,search_query28,863
+    pub yanked: &'a Option<CellValue>,yanked29,894
+    pub dirty: bool,dirty30,933
+    pub search_mode: bool,search_mode31,954
+    pub formula_panel_open: bool,formula_panel_open32,981
+    pub category_panel_open: bool,category_panel_open33,1015
+    pub view_panel_open: bool,view_panel_open34,1050
+    pub formula_cursor: usize,formula_cursor36,1103
+    pub cat_panel_cursor: usize,cat_panel_cursor37,1134
+    pub view_panel_cursor: usize,view_panel_cursor38,1167
+    pub tile_cat_idx: usize,tile_cat_idx40,1257
+    pub buffers: &'a HashMap<String, String>,buffers42,1313
+    pub view_back_stack: &'a [crate::ui::app::ViewFrame],view_back_stack44,1415
+    pub view_forward_stack: &'a [crate::ui::app::ViewFrame],view_forward_stack45,1473
+    pub has_drill_state: bool,has_drill_state47,1598
+    pub display_value: String,display_value49,1713
+    pub visible_rows: usize,visible_rows51,1816
+    pub visible_cols: usize,visible_cols52,1845
+    pub expanded_cats: &'a std::collections::HashSet<String>,expanded_cats54,1920
+    pub key_code: KeyCode,key_code56,2026
+impl<'a> CmdContext<'a> {CmdContext59,2056
+    pub fn is_records_mode(&self) -> bool {is_records_mode61,2152
+    pub fn cell_key(&self) -> Option<CellKey> {cell_key65,2241
+    pub fn synthetic_record_at_cursor(&self) -> Option<(usize, String)> {synthetic_record_at_cursor70,2435
+    pub fn row_count(&self) -> usize {row_count76,2620
+    pub fn col_count(&self) -> usize {col_count79,2697
+    pub fn none_cats(&self) -> &[String] {none_cats82,2774
+impl<'a> CmdContext<'a> {CmdContext87,2857
+    pub fn cat_tree_entry(&self) -> Option<crate::ui::cat_tree::CatTreeEntry> {cat_tree_entry89,2952
+    pub fn cat_at_cursor(&self) -> Option<String> {cat_at_cursor96,3283
+    pub fn cat_tree_len(&self) -> usize {cat_tree_len101,3460
+pub trait Cmd: Debug + Send + Sync {Cmd107,3646
+    fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>>;execute108,3683
+    fn name(&self) -> &'static str;name112,3899
+pub type ParseFn = fn(&[String]) -> Result<Box<dyn Cmd>, String>;ParseFn116,4011
+pub type InteractiveFn = fn(&[String], &CmdContext) -> Result<Box<dyn Cmd>, String>;InteractiveFn122,4350
+type BoxParseFn = Box<dyn Fn(&[String]) -> Result<Box<dyn Cmd>, String>>;BoxParseFn124,4436
+type BoxInteractiveFn = Box<dyn Fn(&[String], &CmdContext) -> Result<Box<dyn Cmd>, String>>;BoxInteractiveFn125,4510
+struct CmdEntry {CmdEntry128,4680
+    name: &'static str,name129,4698
+    parse: BoxParseFn,parse130,4722
+    interactive: BoxInteractiveFn,interactive131,4745
+pub struct CmdRegistry {CmdRegistry136,4880
+    entries: Vec<CmdEntry>,entries137,4905
+    aliases: Vec<(&'static str, &'static str)>,aliases138,4933
+impl CmdRegistry {CmdRegistry141,4984
+    pub fn new() -> Self {new142,5003
+    pub fn alias(&mut self, short: &'static str, canonical: &'static str) {alias150,5201
+    fn resolve<'a>(&'a self, name: &'a str) -> &'a str {resolve155,5387
+    pub fn register(&mut self, prototype: &dyn Cmd, parse: ParseFn, interactive: InteractiveFn) register166,5750
+    pub fn register_pure(&mut self, prototype: &dyn Cmd, parse: ParseFn) {register_pure177,6221
+    pub fn register_nullary(&mut self, f: fn() -> Box<dyn Cmd>) {register_nullary193,6802
+    pub fn parse(&self, name: &str, args: &[String]) -> Result<Box<dyn Cmd>, String> {parse203,7143
+    pub fn interactive(interactive216,7665
+    pub fn names(&self) -> impl Iterator<Item = &'static str> + '_ {names232,8073
+pub(super) struct NamedCmd(pub(super) &'static str);NamedCmd240,8337
+impl Cmd for NamedCmd {NamedCmd241,8390
+    fn name(&self) -> &'static str {name242,8414
+    fn execute(&self, _: &CmdContext) -> Vec<Box<dyn Effect>> {execute245,8472
+pub(super) fn require_args(word: &str, args: &[String], n: usize) -> Result<(), String> {require_args250,8560
+pub(super) fn parse_cell_key_from_args(args: &[String]) -> crate::model::cell::CellKey {parse_cell_key_from_args262,8872
+pub(super) fn read_buffer(ctx: &CmdContext, name: &str) -> String {read_buffer274,9290
+pub(super) fn parse_panel(s: &str) -> Result<Panel, String> {parse_panel282,9502
+pub(super) fn parse_axis(s: &str) -> Result<Axis, String> {parse_axis291,9763
+mod tests {tests302,10085
+    fn parse_axis_recognizes_all_variants() {parse_axis_recognizes_all_variants306,10128
+    fn parse_axis_rejects_unknown() {parse_axis_rejects_unknown316,10462
+
+.claude/worktrees/improvise-ewi-formula-crate/src/command/cmd/commit.rs,2750
+mod tests {tests10,266
+    fn commit_formula_with_categories_adds_formula() {commit_formula_with_categories_adds_formula18,426
+    fn commit_formula_without_regular_categories_targets_measure() {commit_formula_without_regular_categories_targets_measure41,1294
+    fn commit_category_add_with_name_produces_add_effect() {commit_category_add_with_name_produces_add_effect62,2036
+    fn commit_category_add_with_empty_buffer_returns_to_panel() {commit_category_add_with_empty_buffer_returns_to_panel79,2622
+    fn commit_item_add_with_name_produces_add_item() {commit_item_add_with_name_produces_add_item96,3221
+    fn commit_item_add_outside_item_add_mode_returns_empty() {commit_item_add_outside_item_add_mode_returns_empty112,3853
+    fn commit_export_produces_export_and_normal_mode() {commit_export_produces_export_and_normal_mode122,4176
+fn commit_regular_cell_value(key: &CellKey, value: &str, effects: &mut Vec<Box<dyn Effect>>) {commit_regular_cell_value143,5139
+fn stage_drill_edit(record_idx: usize, col_name: String, value: &str) -> Box<dyn Effect> {stage_drill_edit158,5735
+fn commit_plain_records_edit(commit_plain_records_edit167,6034
+fn commit_cell_value(commit_cell_value208,7063
+pub enum AdvanceDir {AdvanceDir228,7649
+    Down,Down230,7741
+    Right,Right232,7800
+pub struct CommitAndAdvance {CommitAndAdvance238,7981
+    pub key: CellKey,key239,8011
+    pub value: String,value240,8033
+    pub advance: AdvanceDir,advance241,8056
+    pub cursor: CursorState,cursor242,8085
+impl Cmd for CommitAndAdvance {CommitAndAdvance244,8116
+    fn name(&self) -> &'static str {name245,8148
+    fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute251,8343
+pub struct CommitFormula;CommitFormula297,10180
+impl Cmd for CommitFormula {CommitFormula298,10206
+    fn name(&self) -> &'static str {name299,10235
+    fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute302,10303
+fn commit_add_from_buffer(commit_add_from_buffer322,11120
+pub struct CommitCategoryAdd;CommitCategoryAdd345,11827
+impl Cmd for CommitCategoryAdd {CommitCategoryAdd346,11857
+    fn name(&self) -> &'static str {name347,11890
+    fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute350,11963
+pub struct CommitItemAdd;CommitItemAdd362,12340
+impl Cmd for CommitItemAdd {CommitItemAdd363,12366
+    fn name(&self) -> &'static str {name364,12395
+    fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute367,12464
+pub struct CommitExport;CommitExport389,13085
+impl Cmd for CommitExport {CommitExport390,13110
+    fn name(&self) -> &'static str {name391,13138
+    fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute394,13205
+
+.claude/worktrees/improvise-ewi-formula-crate/src/command/cmd/cell.rs,1886
+mod tests {tests6,90
+    fn clear_selected_cell_produces_clear_and_dirty() {clear_selected_cell_produces_clear_and_dirty12,229
+    fn yank_cell_produces_set_yanked() {yank_cell_produces_set_yanked30,851
+    fn paste_with_yanked_value_produces_set_cell() {paste_with_yanked_value_produces_set_cell48,1450
+    fn paste_without_yanked_value_produces_nothing() {paste_without_yanked_value_produces_nothing74,2356
+    fn transpose_produces_transpose_and_dirty() {transpose_produces_transpose_and_dirty89,2835
+    fn save_produces_save_effect() {save_produces_save_effect104,3309
+pub struct ClearCellCommand {ClearCellCommand122,4091
+    pub key: crate::model::cell::CellKey,key123,4121
+impl Cmd for ClearCellCommand {ClearCellCommand125,4165
+    fn name(&self) -> &'static str {name126,4197
+    fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute129,4261
+pub struct YankCell {YankCell139,4501
+    pub key: crate::model::cell::CellKey,key140,4523
+impl Cmd for YankCell {YankCell142,4567
+    fn name(&self) -> &'static str {name143,4591
+    fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute146,4649
+pub struct PasteCell {PasteCell157,4974
+    pub key: crate::model::cell::CellKey,key158,4997
+impl Cmd for PasteCell {PasteCell160,5041
+    fn name(&self) -> &'static str {name161,5066
+    fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute164,5125
+pub struct TransposeAxes;TransposeAxes179,5657
+impl Cmd for TransposeAxes {TransposeAxes180,5683
+    fn name(&self) -> &'static str {name181,5712
+    fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute184,5775
+pub struct SaveCmd;SaveCmd190,5936
+impl Cmd for SaveCmd {SaveCmd191,5956
+    fn name(&self) -> &'static str {name192,5979
+    fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute195,6037
+
+.claude/worktrees/improvise-ewi-formula-crate/src/command/cmd/registry.rs,113
+pub fn default_registry() -> CmdRegistry {default_registry19,459
+    macro_rules! reg_jump {reg_jump105,4345
+
+.claude/worktrees/improvise-ewi-formula-crate/src/command/cmd/effect_cmds.rs,518
+mod tests {tests8,162
+    fn add_category_cmd_produces_add_category_effect() {add_category_cmd_produces_add_category_effect13,251
+    fn set_cell_cmd_parses_coords_correctly() {set_cell_cmd_parses_coords_correctly29,780
+    fn set_axis_cmd_recognizes_column_alias() {set_axis_cmd_recognizes_column_alias46,1347
+    fn write_cmd_without_args_saves() {write_cmd_without_args_saves59,1838
+    fn write_cmd_with_path_saves_as() {write_cmd_with_path_saves_as72,2276
+macro_rules! effect_cmd {effect_cmd89,3051
+
+.claude/worktrees/improvise-ewi-formula-crate/src/command/cmd/tile.rs,1057
+mod tests {tests7,113
+    fn tile_axis_cycle_produces_cycle_effect() {tile_axis_cycle_produces_cycle_effect12,202
+    fn tile_axis_set_produces_set_axis_effect() {tile_axis_set_produces_set_axis_effect25,668
+    fn tile_axis_with_out_of_bounds_cursor_returns_empty() {tile_axis_with_out_of_bounds_cursor_returns_empty40,1177
+    fn move_tile_cursor_right() {move_tile_cursor_right52,1569
+    fn move_tile_cursor_clamps_at_start() {move_tile_cursor_clamps_at_start68,2049
+pub struct MoveTileCursor(pub i32);MoveTileCursor88,2790
+impl Cmd for MoveTileCursor {MoveTileCursor89,2826
+    fn name(&self) -> &'static str {name90,2856
+    fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute93,2926
+pub struct TileAxisOp {TileAxisOp107,3465
+    pub axis: Option<Axis>,axis108,3489
+fn axis_label(axis: Axis) -> &'static str {axis_label111,3520
+impl Cmd for TileAxisOp {TileAxisOp120,3709
+    fn name(&self) -> &'static str {name121,3735
+    fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute128,3904
+
+.claude/worktrees/improvise-ewi-formula-crate/src/command/cmd/grid.rs,2719
+mod tests {tests9,182
+    fn toggle_group_under_cursor_returns_empty_without_groups() {toggle_group_under_cursor_returns_empty_without_groups14,271
+    fn law_toggle_group_involution() {law_toggle_group_involution25,643
+    fn view_forward_with_empty_stack_shows_status() {view_forward_with_empty_stack_shows_status37,1100
+    fn view_back_with_empty_stack_shows_status() {view_back_with_empty_stack_shows_status52,1583
+    fn view_forward_with_stack_produces_effect() {view_forward_with_stack_produces_effect67,2065
+    fn view_back_with_stack_produces_apply_and_back() {view_back_with_stack_produces_apply_and_back87,2752
+    fn toggle_prune_empty_produces_toggle_and_dirty() {toggle_prune_empty_produces_toggle_and_dirty109,3573
+    fn drill_into_formula_cell_returns_data_records() {drill_into_formula_cell_returns_data_records127,4308
+pub struct ToggleGroupAtCursor {ToggleGroupAtCursor178,6275
+    pub is_row: bool,is_row179,6308
+impl Cmd for ToggleGroupAtCursor {ToggleGroupAtCursor181,6332
+    fn name(&self) -> &'static str {name182,6367
+    fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute189,6546
+pub struct HideSelectedRowItem;HideSelectedRowItem210,7103
+impl Cmd for HideSelectedRowItem {HideSelectedRowItem211,7135
+    fn name(&self) -> &'static str {name212,7170
+    fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute215,7246
+pub struct ViewNavigate {ViewNavigate248,8123
+    pub forward: bool,forward249,8149
+impl Cmd for ViewNavigate {ViewNavigate251,8174
+    fn name(&self) -> &'static str {name252,8202
+    fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute259,8349
+pub struct DrillIntoCell {DrillIntoCell283,9178
+    pub key: crate::model::cell::CellKey,key284,9205
+impl Cmd for DrillIntoCell {DrillIntoCell286,9249
+    fn name(&self) -> &'static str {name287,9278
+    fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute290,9347
+pub struct TogglePruneEmpty;TogglePruneEmpty380,12781
+impl Cmd for TogglePruneEmpty {TogglePruneEmpty381,12810
+    fn name(&self) -> &'static str {name382,12842
+    fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute385,12914
+pub struct ToggleRecordsMode;ToggleRecordsMode402,13503
+impl Cmd for ToggleRecordsMode {ToggleRecordsMode403,13533
+    fn name(&self) -> &'static str {name404,13566
+    fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute407,13639
+pub struct AddRecordRow;AddRecordRow450,15300
+impl Cmd for AddRecordRow {AddRecordRow451,15325
+    fn name(&self) -> &'static str {name452,15353
+    fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute455,15421
+
+.claude/worktrees/improvise-ewi-formula-crate/src/command/cmd/text_buffer.rs,1949
+mod tests {tests9,164
+    fn command_mode_backspace_pops_char() {command_mode_backspace_pops_char16,289
+    fn command_mode_backspace_on_empty_returns_to_normal() {command_mode_backspace_on_empty_returns_to_normal31,895
+    fn quit_when_dirty_shows_warning() {quit_when_dirty_shows_warning48,1477
+    fn quit_when_clean_produces_quit_mode() {quit_when_clean_produces_quit_mode64,2046
+    fn execute_command_empty_returns_to_normal() {execute_command_empty_returns_to_normal81,2600
+    fn execute_command_invalid_shows_error_status() {execute_command_invalid_shows_error_status95,3127
+    fn execute_command_valid_runs_command() {execute_command_valid_runs_command112,3719
+pub struct AppendChar {AppendChar131,4365
+    pub buffer: String,buffer132,4389
+impl Cmd for AppendChar {AppendChar134,4415
+    fn name(&self) -> &'static str {name135,4441
+    fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute138,4506
+pub struct PopChar {PopChar158,5109
+    pub buffer: String,buffer159,5130
+impl Cmd for PopChar {PopChar161,5156
+    fn name(&self) -> &'static str {name162,5179
+    fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute165,5241
+pub struct CommandModeBackspace;CommandModeBackspace181,5739
+impl Cmd for CommandModeBackspace {CommandModeBackspace182,5772
+    fn name(&self) -> &'static str {name183,5808
+    fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute186,5884
+pub struct HandleWizardKey;HandleWizardKey205,6609
+impl Cmd for HandleWizardKey {HandleWizardKey206,6637
+    fn name(&self) -> &'static str {name207,6668
+    fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute210,6739
+pub struct ExecuteCommand;ExecuteCommand224,7365
+impl Cmd for ExecuteCommand {ExecuteCommand225,7392
+    fn name(&self) -> &'static str {name226,7422
+    fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute229,7491
+
+.claude/worktrees/improvise-ewi-formula-crate/src/command/cmd/mod.rs,1111
+pub mod cell;cell1,0
+pub mod commit;commit2,14
+pub mod core;core3,30
+pub mod effect_cmds;effect_cmds4,44
+pub mod grid;grid5,65
+pub mod mode;mode6,79
+pub mod navigation;navigation7,93
+pub mod panel;panel8,113
+pub mod registry;registry9,128
+pub mod search;search10,146
+pub mod text_buffer;text_buffer11,162
+pub mod tile;tile12,183
+pub(super) mod test_helpers {test_helpers19,341
+    pub type CmdRegistry = super::core::CmdRegistry;CmdRegistry32,657
+    pub static EMPTY_BUFFERS: std::sync::LazyLock<HashMap<String, String>> =EMPTY_BUFFERS34,711
+    pub static EMPTY_EXPANDED: std::sync::LazyLock<std::collections::HashSet<String>> =EMPTY_EXPANDED36,836
+    pub fn make_layout(workbook: &Workbook) -> GridLayout {make_layout39,991
+    pub fn make_ctx<'a>(make_ctx43,1123
+    pub fn two_cat_model() -> Workbook {two_cat_model89,2586
+    pub fn three_cat_model_with_page() -> Workbook {three_cat_model_with_page100,3038
+    pub fn effects_debug(effects: &[Box<dyn Effect>]) -> String {effects_debug117,3836
+    pub fn make_registry() -> CmdRegistry {make_registry121,3942
+
+.claude/worktrees/improvise-ewi-formula-crate/src/command/cmd/navigation.rs,2756
+pub struct CursorState {CursorState12,526
+    pub row: usize,row13,551
+    pub col: usize,col14,571
+    pub row_count: usize,row_count15,591
+    pub col_count: usize,col_count16,617
+    pub row_offset: usize,row_offset17,643
+    pub col_offset: usize,col_offset18,670
+    pub visible_rows: usize,visible_rows19,697
+    pub visible_cols: usize,visible_cols20,726
+impl CursorState {CursorState23,758
+    pub fn from_ctx(ctx: &CmdContext) -> Self {from_ctx24,777
+pub(super) fn viewport_effects(viewport_effects39,1239
+pub enum MoveKind {MoveKind75,2232
+    Relative(i32, i32),Relative77,2328
+    ToStart(bool),ToStart79,2412
+    ToEnd(bool),ToEnd81,2489
+    Page(i32),Page83,2582
+pub struct Move {Move88,2693
+    pub kind: MoveKind,kind89,2711
+    pub cursor: CursorState,cursor90,2735
+    pub cmd_name: &'static str,cmd_name91,2764
+impl Cmd for Move {Move94,2799
+    fn name(&self) -> &'static str {name95,2819
+    fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute99,2885
+pub struct EnterAdvance {EnterAdvance141,4438
+    pub cursor: CursorState,cursor142,4464
+impl Cmd for EnterAdvance {EnterAdvance144,4495
+    fn name(&self) -> &'static str {name145,4523
+    fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute148,4590
+pub struct PageNext;PageNext174,5522
+impl Cmd for PageNext {PageNext175,5543
+    fn name(&self) -> &'static str {name176,5567
+    fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute179,5630
+pub struct PagePrev;PagePrev211,6589
+impl Cmd for PagePrev {PagePrev212,6610
+    fn name(&self) -> &'static str {name213,6634
+    fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute216,6697
+pub(super) fn page_cat_data(ctx: &CmdContext) -> Vec<(String, Vec<String>, usize)> {page_cat_data247,7678
+mod tests {tests282,8760
+    fn move_selection_down_produces_set_selected() {move_selection_down_produces_set_selected287,8849
+    fn move_selection_clamps_to_bounds() {move_selection_clamps_to_bounds302,9322
+    fn enter_advance_moves_down() {enter_advance_moves_down317,9789
+    fn law_move_to_start_idempotent() {law_move_to_start_idempotent335,10332
+    fn law_sequence_associativity() {law_sequence_associativity359,11135
+    fn law_move_to_end_reaches_last_col() {law_move_to_end_reaches_last_col407,12427
+    fn page_next_with_no_page_cats_returns_empty() {page_next_with_no_page_cats_returns_empty427,13120
+    fn page_prev_with_no_page_cats_returns_empty() {page_prev_with_no_page_cats_returns_empty437,13428
+    fn page_next_cycles_through_page_items() {page_next_cycles_through_page_items447,13736
+    fn page_prev_cycles_backward() {page_prev_cycles_backward462,14220
+
+.claude/worktrees/improvise-ewi-formula-crate/src/command/cmd/panel.rs,5404
+mod tests {tests7,126
+    fn toggle_panel_open_and_focus() {toggle_panel_open_and_focus13,242
+    fn toggle_panel_close_and_unfocus() {toggle_panel_close_and_unfocus33,847
+    fn cycle_panel_focus_with_no_panels_open() {cycle_panel_focus_with_no_panels_open48,1287
+    fn cycle_panel_focus_with_formula_panel_open() {cycle_panel_focus_with_formula_panel_open63,1730
+    fn cycle_panel_focus_with_multiple_panels() {cycle_panel_focus_with_multiple_panels84,2385
+    fn move_panel_cursor_down_from_zero() {move_panel_cursor_down_from_zero106,3104
+    fn move_panel_cursor_clamps_at_zero() {move_panel_cursor_clamps_at_zero127,3720
+    fn move_panel_cursor_with_zero_max_produces_nothing() {move_panel_cursor_with_zero_max_produces_nothing143,4171
+    fn delete_formula_at_cursor_with_formulas() {delete_formula_at_cursor_with_formulas159,4637
+    fn switch_view_at_cursor_with_valid_cursor() {switch_view_at_cursor_with_valid_cursor180,5387
+    fn switch_view_at_cursor_out_of_bounds_returns_empty() {switch_view_at_cursor_out_of_bounds_returns_empty195,5900
+    fn create_and_switch_view_names_incrementally() {create_and_switch_view_names_incrementally206,6267
+    fn delete_view_at_cursor_zero_does_not_adjust_cursor() {delete_view_at_cursor_zero_does_not_adjust_cursor228,6937
+pub struct TogglePanelAndFocus {TogglePanelAndFocus250,7795
+    pub panel: Panel,panel251,7828
+    pub open: bool,open252,7850
+    pub focused: bool,focused253,7870
+impl Cmd for TogglePanelAndFocus {TogglePanelAndFocus255,7895
+    fn name(&self) -> &'static str {name256,7930
+    fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute259,8006
+pub struct TogglePanelVisibility {TogglePanelVisibility276,8539
+    pub panel: Panel,panel277,8574
+    pub currently_open: bool,currently_open278,8596
+impl Cmd for TogglePanelVisibility {TogglePanelVisibility280,8628
+    fn name(&self) -> &'static str {name281,8665
+    fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute284,8742
+pub struct CyclePanelFocus {CyclePanelFocus294,9030
+    pub formula_open: bool,formula_open295,9059
+    pub category_open: bool,category_open296,9087
+    pub view_open: bool,view_open297,9116
+impl Cmd for CyclePanelFocus {CyclePanelFocus299,9143
+    fn name(&self) -> &'static str {name300,9174
+    fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute303,9245
+pub struct MovePanelCursor {MovePanelCursor320,9914
+    pub panel: Panel,panel321,9943
+    pub delta: i32,delta322,9965
+    pub current: usize,current323,9985
+    pub max: usize,max324,10009
+impl Cmd for MovePanelCursor {MovePanelCursor326,10031
+    fn name(&self) -> &'static str {name327,10062
+    fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute330,10133
+pub struct EnterFormulaEdit;EnterFormulaEdit353,10929
+impl Cmd for EnterFormulaEdit {EnterFormulaEdit354,10958
+    fn name(&self) -> &'static str {name355,10990
+    fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute358,11062
+pub struct DeleteFormulaAtCursor;DeleteFormulaAtCursor365,11269
+impl Cmd for DeleteFormulaAtCursor {DeleteFormulaAtCursor366,11303
+    fn name(&self) -> &'static str {name367,11340
+    fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute370,11418
+pub struct CycleAxisAtCursor;CycleAxisAtCursor399,12519
+impl Cmd for CycleAxisAtCursor {CycleAxisAtCursor400,12549
+    fn name(&self) -> &'static str {name401,12582
+    fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute404,12656
+pub struct OpenItemAddAtCursor;OpenItemAddAtCursor417,13077
+impl Cmd for OpenItemAddAtCursor {OpenItemAddAtCursor418,13109
+    fn name(&self) -> &'static str {name419,13144
+    fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute422,13221
+pub struct ToggleCatExpand;ToggleCatExpand435,13650
+impl Cmd for ToggleCatExpand {ToggleCatExpand436,13678
+    fn name(&self) -> &'static str {name437,13709
+    fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute440,13780
+pub struct FilterToItem;FilterToItem452,14139
+impl Cmd for FilterToItem {FilterToItem453,14164
+    fn name(&self) -> &'static str {name454,14192
+    fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute457,14260
+pub struct DeleteCategoryAtCursor;DeleteCategoryAtCursor489,15414
+impl Cmd for DeleteCategoryAtCursor {DeleteCategoryAtCursor490,15449
+    fn name(&self) -> &'static str {name491,15487
+    fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute494,15566
+pub struct SwitchViewAtCursor;SwitchViewAtCursor526,16864
+impl Cmd for SwitchViewAtCursor {SwitchViewAtCursor527,16895
+    fn name(&self) -> &'static str {name528,16929
+    fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute531,17004
+pub struct CreateAndSwitchView;CreateAndSwitchView546,17504
+impl Cmd for CreateAndSwitchView {CreateAndSwitchView547,17536
+    fn name(&self) -> &'static str {name548,17571
+    fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute551,17647
+pub struct DeleteViewAtCursor;DeleteViewAtCursor564,18061
+impl Cmd for DeleteViewAtCursor {DeleteViewAtCursor565,18092
+    fn name(&self) -> &'static str {name566,18126
+    fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute569,18201
+
+.claude/worktrees/improvise-ewi-formula-crate/src/command/cmd/search.rs,1198
+mod tests {tests8,161
+    fn search_navigate_with_empty_query_returns_nothing() {search_navigate_with_empty_query_returns_nothing14,300
+    fn search_or_category_add_without_query_opens_category_add() {search_or_category_add_without_query_opens_category_add25,650
+    fn exit_search_mode_clears_flag() {exit_search_mode_clears_flag41,1170
+    fn search_navigate_forward_with_matching_value() {search_navigate_forward_with_matching_value56,1644
+pub struct SearchNavigate(pub bool);SearchNavigate90,2711
+impl Cmd for SearchNavigate {SearchNavigate91,2748
+    fn name(&self) -> &'static str {name92,2778
+    fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute95,2847
+pub struct SearchOrCategoryAdd;SearchOrCategoryAdd172,5473
+impl Cmd for SearchOrCategoryAdd {SearchOrCategoryAdd173,5505
+    fn name(&self) -> &'static str {name174,5540
+    fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute177,5616
+pub struct ExitSearchMode;ExitSearchMode194,6110
+impl Cmd for ExitSearchMode {ExitSearchMode195,6137
+    fn name(&self) -> &'static str {name196,6167
+    fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute199,6237
+
+.claude/worktrees/improvise-ewi-formula-crate/src/command/cmd/mode.rs,3175
+mod tests {tests8,151
+    fn enter_edit_mode_produces_editing_mode() {enter_edit_mode_produces_editing_mode14,275
+    fn enter_tile_select_with_categories() {enter_tile_select_with_categories29,788
+    fn enter_tile_select_no_categories() {enter_tile_select_no_categories45,1285
+    fn enter_export_prompt_sets_mode() {enter_export_prompt_sets_mode56,1621
+    fn force_quit_always_produces_quit_mode() {force_quit_always_produces_quit_mode70,2055
+    fn save_and_quit_produces_save_then_quit() {save_and_quit_produces_save_then_quit83,2506
+    fn edit_or_drill_without_aggregation_enters_edit() {edit_or_drill_without_aggregation_enters_edit96,2993
+    fn enter_search_mode_sets_flag_and_clears_query() {enter_search_mode_sets_flag_and_clears_query107,3393
+pub struct EnterMode(pub AppMode);EnterMode129,4200
+impl Cmd for EnterMode {EnterMode130,4235
+    fn name(&self) -> &'static str {name131,4260
+    fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute134,4324
+pub struct ForceQuit;ForceQuit149,4842
+impl Cmd for ForceQuit {ForceQuit150,4864
+    fn name(&self) -> &'static str {name151,4889
+    fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute154,4953
+pub struct Quit;Quit161,5159
+impl Cmd for Quit {Quit162,5176
+    fn name(&self) -> &'static str {name163,5196
+    fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute166,5251
+pub struct SaveAndQuit;SaveAndQuit179,5597
+impl Cmd for SaveAndQuit {SaveAndQuit180,5621
+    fn name(&self) -> &'static str {name181,5648
+    fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute184,5704
+pub struct EnterEditMode {EnterEditMode193,6114
+    pub initial_value: String,initial_value194,6141
+impl Cmd for EnterEditMode {EnterEditMode196,6174
+    fn name(&self) -> &'static str {name197,6203
+    fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute200,6272
+pub struct EditOrDrill;EditOrDrill220,6953
+impl Cmd for EditOrDrill {EditOrDrill221,6977
+    fn name(&self) -> &'static str {name222,7004
+    fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute225,7071
+pub struct EnterEditAtCursorCmd;EnterEditAtCursorCmd254,8262
+impl Cmd for EnterEditAtCursorCmd {EnterEditAtCursorCmd255,8295
+    fn name(&self) -> &'static str {name256,8331
+    fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute259,8405
+pub struct EnterExportPrompt;EnterExportPrompt266,8578
+impl Cmd for EnterExportPrompt {EnterExportPrompt267,8608
+    fn name(&self) -> &'static str {name268,8641
+    fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute271,8714
+pub struct EnterSearchMode;EnterSearchMode278,8890
+impl Cmd for EnterSearchMode {EnterSearchMode279,8918
+    fn name(&self) -> &'static str {name280,8949
+    fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute283,9009
+pub struct EnterTileSelect;EnterTileSelect293,9266
+impl Cmd for EnterTileSelect {EnterTileSelect294,9294
+    fn name(&self) -> &'static str {name295,9325
+    fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute298,9396
+
+.claude/worktrees/improvise-ewi-formula-crate/src/command/keymap.rs,5088
+fn format_key_label(code: &KeyCode) -> String {format_key_label13,301
+pub enum KeyPattern {KeyPattern38,1303
+    Key(KeyCode, KeyModifiers),Key40,1359
+    AnyChar,AnyChar42,1444
+    Any,Any44,1516
+pub enum ModeKey {ModeKey49,1621
+    Normal,Normal50,1640
+    Help,Help51,1652
+    FormulaPanel,FormulaPanel52,1662
+    CategoryPanel,CategoryPanel53,1680
+    ViewPanel,ViewPanel54,1699
+    TileSelect,TileSelect55,1714
+    Editing,Editing56,1730
+    FormulaEdit,FormulaEdit57,1743
+    CategoryAdd,CategoryAdd58,1760
+    ItemAdd,ItemAdd59,1777
+    ExportPrompt,ExportPrompt60,1790
+    CommandMode,CommandMode61,1808
+    SearchMode,SearchMode62,1825
+    ImportWizard,ImportWizard63,1841
+    RecordsNormal,RecordsNormal64,1859
+    RecordsEditing,RecordsEditing65,1878
+impl ModeKey {ModeKey68,1901
+    pub fn from_app_mode(mode: &AppMode, search_mode: bool) -> Option<Self> {from_app_mode69,1916
+pub enum Binding {Binding94,3173
+    Cmd {Cmd96,3272
+    Prefix(Arc<Keymap>),Prefix101,3387
+    Sequence(Vec<(&'static str, Vec<String>)>),Sequence103,3491
+pub struct Keymap {Keymap110,3773
+    bindings: HashMap<KeyPattern, Binding>,bindings111,3793
+    parent: Option<Arc<Keymap>>,parent112,3837
+impl fmt::Debug for Keymap {Keymap115,3873
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {fmt116,3902
+impl Keymap {Keymap123,4087
+    pub fn new() -> Self {new124,4101
+    pub fn with_parent(parent: Arc<Keymap>) -> Self {with_parent133,4366
+    pub fn bind(&mut self, key: KeyCode, mods: KeyModifiers, name: &'static str) {bind141,4572
+    pub fn bind_args(bind_args149,4845
+    pub fn bind_prefix(&mut self, key: KeyCode, mods: KeyModifiers, sub: Arc<Keymap>) {bind_prefix161,5161
+    pub fn bind_seq(bind_seq167,5415
+    pub fn bind_any_char(&mut self, name: &'static str, args: Vec<String>) {bind_any_char178,5709
+    pub fn bind_any(&mut self, name: &'static str) {bind_any184,5931
+    pub fn binding_hints(&self) -> Vec<(String, &'static str)> {binding_hints191,6252
+        let mut hints: Vec<(String, &'static str)> = selfstr192,6317
+    fn lookup_local(&self, key: KeyCode, mods: KeyModifiers) -> Option<&Binding> {lookup_local219,7327
+    pub fn lookup(&self, key: KeyCode, mods: KeyModifiers) -> Option<&Binding> {lookup241,8200
+    pub fn dispatch(dispatch247,8488
+pub struct SetTransientKeymap(pub Arc<Keymap>);SetTransientKeymap275,9438
+impl Effect for SetTransientKeymap {SetTransientKeymap277,9487
+    fn apply(&self, app: &mut crate::ui::app::App) {apply278,9524
+pub struct KeymapSet {KeymapSet284,9705
+    mode_maps: HashMap<ModeKey, Arc<Keymap>>,mode_maps285,9728
+    registry: CmdRegistry,registry286,9774
+impl KeymapSet {KeymapSet289,9804
+    pub fn new(registry: CmdRegistry) -> Self {new290,9821
+    pub fn registry(&self) -> &CmdRegistry {registry297,9962
+    pub fn insert(&mut self, mode: ModeKey, keymap: Arc<Keymap>) {insert301,10037
+    pub fn dispatch(dispatch306,10224
+    pub fn dispatch_transient(dispatch_transient318,10642
+    pub fn default_keymaps() -> Self {default_keymaps329,10948
+mod tests {tests930,33396
+    fn lookup_exact_match() {lookup_exact_match936,33587
+    fn lookup_exact_with_ctrl() {lookup_exact_with_ctrl944,33877
+    fn lookup_char_falls_back_to_none_mods() {lookup_char_falls_back_to_none_mods952,34175
+    fn lookup_non_char_does_not_fall_back_to_none_mods() {lookup_non_char_does_not_fall_back_to_none_mods962,34633
+    fn lookup_char_falls_to_any_char() {lookup_char_falls_to_any_char972,35046
+    fn lookup_non_char_skips_any_char() {lookup_non_char_skips_any_char986,35414
+    fn lookup_any_matches_everything() {lookup_any_matches_everything995,35722
+    fn lookup_exact_takes_priority_over_any_char() {lookup_exact_takes_priority_over_any_char1009,36067
+    fn lookup_any_char_takes_priority_over_any() {lookup_any_char_takes_priority_over_any1024,36509
+    fn lookup_non_char_falls_to_any_not_any_char() {lookup_non_char_falls_to_any_not_any_char1039,36921
+    fn lookup_ctrl_char_with_only_none_binding_falls_through() {lookup_ctrl_char_with_only_none_binding_falls_through1054,37330
+    fn mode_key_normal_no_search() {mode_key_normal_no_search1065,37836
+    fn mode_key_normal_with_search_overrides() {mode_key_normal_with_search_overrides1071,38005
+    fn mode_key_help() {mode_key_help1077,38189
+    fn mode_key_quit_returns_none() {mode_key_quit_returns_none1083,38342
+    fn lookup_returns_prefix_binding() {lookup_returns_prefix_binding1091,38669
+    fn lookup_returns_sequence_binding() {lookup_returns_sequence_binding1100,39000
+    fn default_keymaps_has_all_modes() {default_keymaps_has_all_modes1114,39551
+    fn normal_mode_has_basic_movement() {normal_mode_has_basic_movement1144,40436
+    fn editing_mode_has_any_char_and_esc() {editing_mode_has_any_char_and_esc1168,41220
+    fn search_mode_has_any_char_and_esc() {search_mode_has_any_char_and_esc1182,41696
+    fn import_wizard_has_any_catchall() {import_wizard_has_any_catchall1194,42090
+
+.claude/worktrees/improvise-ewi-formula-crate/src/command/mod.rs,80
+pub mod cmd;cmd8,323
+pub mod keymap;keymap9,336
+pub mod parse;parse10,352
+
+.claude/worktrees/improvise-ewi-formula-crate/src/command/parse.rs,1793
+pub fn parse_line(line: &str) -> Result<Vec<Box<dyn Cmd>>, String> {parse_line11,345
+pub fn parse_line_with(registry: &CmdRegistry, line: &str) -> Result<Vec<Box<dyn Cmd>>, String> parse_line_with17,548
+fn split_on_dot(line: &str) -> Vec<&str> {split_on_dot42,1353
+fn tokenize(input: &str) -> Vec<String> {tokenize67,2137
+mod tests {tests102,3018
+    fn parse_add_category() {parse_add_category106,3061
+    fn parse_add_item() {parse_add_item113,3260
+    fn parse_set_cell_number() {parse_set_cell_number120,3452
+    fn parse_set_cell_text() {parse_set_cell_text127,3671
+    fn parse_multiple_commands_dot_separated() {parse_multiple_commands_dot_separated134,3874
+    fn parse_quoted_string() {parse_quoted_string142,4163
+    fn parse_set_axis() {parse_set_axis149,4391
+    fn parse_set_axis_none() {parse_set_axis_none155,4546
+    fn parse_clear_cell() {parse_clear_cell161,4706
+    fn parse_comments_and_blank_lines() {parse_comments_and_blank_lines168,4920
+    fn parse_unknown_command_errors() {parse_unknown_command_errors175,5159
+    fn parse_missing_args_errors() {parse_missing_args_errors180,5274
+    fn alias_add_cat_resolves_to_add_category() {alias_add_cat_resolves_to_add_category188,5615
+    fn alias_formula_resolves_to_add_formula() {alias_formula_resolves_to_add_formula195,5829
+    fn alias_add_view_resolves_to_create_view() {alias_add_view_resolves_to_create_view202,6061
+    fn alias_q_bang_resolves_to_force_quit() {alias_q_bang_resolves_to_force_quit209,6275
+    fn alias_does_not_interfere_with_canonical_q() {alias_does_not_interfere_with_canonical_q216,6472
+    fn parse_add_items_multiple() {parse_add_items_multiple225,6840
+    fn add_items_requires_at_least_two_args() {add_items_requires_at_least_two_args232,7056
+
+.claude/worktrees/improvise-ewi-formula-crate/src/import/analyzer.rs,2068
+pub enum FieldKind {FieldKind6,125
+    Category,Category8,216
+    Measure,Measure10,265
+    TimeCategory,TimeCategory12,322
+    Label,Label14,402
+pub enum DateComponent {DateComponent19,522
+    Year,Year20,547
+    Month,Month21,557
+    Quarter,Quarter22,568
+pub struct FieldProposal {FieldProposal26,608
+    pub field: String,field27,635
+    pub kind: FieldKind,kind28,658
+    pub distinct_values: Vec<String>,distinct_values29,683
+    pub accepted: bool,accepted30,721
+    pub date_format: Option<String>,date_format32,830
+    pub date_components: Vec<DateComponent>,date_components34,927
+impl FieldProposal {FieldProposal37,975
+    pub fn kind_label(&self) -> &'static str {kind_label38,996
+const DATE_FORMATS: &[&str] = &[DATE_FORMATS49,1382
+pub fn detect_date_format(samples: &[&str]) -> Option<String> {detect_date_format62,1806
+pub fn extract_date_component(extract_date_component81,2405
+const CATEGORY_THRESHOLD: usize = 20;CATEGORY_THRESHOLD97,2900
+pub fn analyze_records(records: &[Value]) -> Vec<FieldProposal> {analyze_records99,2939
+pub fn extract_array_at_path<'a>(value: &'a Value, path: &str) -> Option<&'a Vec<Value>> {extract_array_at_path190,5979
+pub fn find_array_paths(value: &Value) -> Vec<String> {find_array_paths202,6310
+fn find_array_paths_inner(value: &Value, prefix: &str, paths: &mut Vec<String>) {find_array_paths_inner208,6462
+mod tests {tests228,7006
+    fn detect_iso_date_format() {detect_iso_date_format232,7049
+    fn detect_us_date_format() {detect_us_date_format238,7252
+    fn detect_short_year_format() {detect_short_year_format244,7454
+    fn detect_no_date_format() {detect_no_date_format252,7779
+    fn extract_year_component() {extract_year_component258,7933
+    fn extract_month_component() {extract_month_component264,8132
+    fn extract_quarter_component() {extract_quarter_component270,8336
+    fn extract_quarter_q4() {extract_quarter_q4276,8544
+    fn analyze_detects_time_category_with_format() {analyze_detects_time_category_with_format282,8745
+
+.claude/worktrees/improvise-ewi-formula-crate/src/import/csv_parser.rs,1361
+pub fn csv_path_p(path: &Path) -> bool {csv_path_p7,101
+pub fn parse_csv(path: &Path) -> Result<Vec<Value>> {parse_csv13,293
+pub fn merge_csvs(paths: &[impl AsRef<Path>]) -> Result<Vec<Value>> {merge_csvs61,1792
+fn parse_csv_field(field: &str) -> Value {parse_csv_field81,2406
+mod tests {tests102,2935
+    fn create_temp_csv(content: &str) -> (PathBuf, tempfile::TempDir) {create_temp_csv107,3027
+    fn parse_simple_csv() {parse_simple_csv115,3268
+    fn parse_csv_with_floats() {parse_csv_with_floats130,3801
+    fn parse_csv_with_quoted_fields() {parse_csv_with_quoted_fields144,4250
+    fn parse_csv_with_empty_values() {parse_csv_with_empty_values158,4721
+    fn parse_csv_mixed_types() {parse_csv_mixed_types168,5075
+    fn merge_csvs_adds_file_field_from_stem() {merge_csvs_adds_file_field_from_stem184,5660
+    fn merge_csvs_single_file_works() {merge_csvs_single_file_works206,6668
+    fn rfc4180_embedded_comma_in_quoted_field() {rfc4180_embedded_comma_in_quoted_field220,7270
+    fn rfc4180_escaped_quotes_in_field() {rfc4180_escaped_quotes_in_field233,7756
+    fn rfc4180_newline_in_quoted_field() {rfc4180_newline_in_quoted_field246,8246
+    fn rfc4180_embedded_comma_and_quotes_combined() {rfc4180_embedded_comma_and_quotes_combined258,8669
+    fn parse_checking_csv_format() {parse_checking_csv_format274,9197
+
+.claude/worktrees/improvise-ewi-formula-crate/src/import/mod.rs,95
+pub mod analyzer;analyzer1,0
+pub mod csv_parser;csv_parser2,18
+pub mod wizard;wizard3,38
+
+.claude/worktrees/improvise-ewi-formula-crate/src/import/wizard.rs,6787
+pub struct ImportPipeline {ImportPipeline17,664
+    pub raw: Value,raw18,692
+    pub array_paths: Vec<String>,array_paths19,712
+    pub selected_path: String,selected_path20,746
+    pub records: Vec<Value>,records21,777
+    pub proposals: Vec<FieldProposal>,proposals22,806
+    pub model_name: String,model_name23,845
+    pub formulas: Vec<String>,formulas25,956
+impl ImportPipeline {ImportPipeline28,990
+    pub fn new(raw: Value) -> Self {new29,1012
+    pub fn select_path(&mut self, path: &str) {select_path52,1719
+    pub fn needs_path_selection(&self) -> bool {needs_path_selection60,2000
+    pub fn preview_summary(&self) -> String {preview_summary64,2143
+    pub fn build_model(&self) -> Result<Workbook> {build_model84,2925
+pub enum WizardStep {WizardStep247,9238
+    Preview,Preview248,9260
+    SelectArrayPath,SelectArrayPath249,9273
+    ReviewProposals,ReviewProposals250,9294
+    ConfigureDates,ConfigureDates251,9315
+    DefineFormulas,DefineFormulas252,9335
+    NameModel,NameModel253,9355
+    Done,Done254,9370
+pub struct ImportWizard {ImportWizard261,9636
+    pub pipeline: ImportPipeline,pipeline262,9662
+    pub step: WizardStep,step263,9696
+    pub cursor: usize,cursor265,9789
+    pub message: Option<String>,message267,9883
+    pub formula_editing: bool,formula_editing269,9966
+    pub formula_buffer: String,formula_buffer271,10041
+impl ImportWizard {ImportWizard274,10076
+    pub fn new(raw: Value) -> Self {new275,10096
+    pub fn advance(&mut self) {advance298,10824
+    fn has_time_categories(&self) -> bool {has_time_categories324,11809
+    pub fn time_category_proposals(&self) -> Vec<&FieldProposal> {time_category_proposals332,12093
+    pub fn confirm_path(&mut self) {confirm_path340,12355
+    pub fn move_cursor(&mut self, delta: i32) {move_cursor350,12810
+    pub fn toggle_proposal(&mut self) {toggle_proposal370,13583
+    pub fn cycle_proposal_kind(&mut self) {cycle_proposal_kind377,13821
+    pub fn push_name_char(&mut self, c: char) {push_name_char391,14488
+    pub fn pop_name_char(&mut self) {pop_name_char394,14584
+    fn date_config_item_count(&self) -> usize {date_config_item_count402,15003
+    pub fn date_config_at_cursor(&self) -> Option<(usize, DateComponent)> {date_config_at_cursor407,15181
+    fn time_category_indices(&self) -> Vec<usize> {time_category_indices423,15775
+    pub fn toggle_date_component(&mut self) {toggle_date_component436,16182
+    pub fn push_formula_char(&mut self, c: char) {push_formula_char454,16949
+    pub fn pop_formula_char(&mut self) {pop_formula_char462,17171
+    pub fn confirm_formula(&mut self) {confirm_formula467,17328
+    pub fn delete_formula(&mut self) {delete_formula478,17725
+    pub fn start_formula_edit(&mut self) {start_formula_edit488,18058
+    pub fn cancel_formula_edit(&mut self) {cancel_formula_edit494,18214
+    pub fn sample_formulas(&self) -> Vec<String> {sample_formulas500,18401
+    pub fn build_model(&self) -> Result<Workbook> {build_model524,19280
+mod tests {tests530,19390
+    fn flat_array_auto_selected() {flat_array_auto_selected536,19516
+    fn numeric_field_proposed_as_measure() {numeric_field_proposed_as_measure548,19919
+    fn low_cardinality_string_field_proposed_as_category() {low_cardinality_string_field_proposed_as_category556,20240
+    fn nested_json_needs_path_selection_when_multiple_arrays() {nested_json_needs_path_selection_when_multiple_arrays568,20684
+    fn single_nested_array_auto_selected() {single_nested_array_auto_selected578,20995
+    fn select_path_populates_records_and_proposals() {select_path_populates_records_and_proposals591,21367
+    fn build_model_fails_with_no_accepted_categories() {build_model_fails_with_no_accepted_categories603,21721
+    fn build_model_creates_categories_and_measure_category() {build_model_creates_categories_and_measure_category613,22031
+    fn label_fields_imported_as_label_category_coords() {label_fields_imported_as_label_category_coords625,22450
+    fn label_category_defaults_to_none_axis() {label_category_defaults_to_none_axis652,23758
+    fn build_model_cells_match_source_data() {build_model_cells_match_source_data665,24240
+    fn model_name_defaults_to_imported_model() {model_name_defaults_to_imported_model692,25148
+    fn build_model_adds_formulas_from_pipeline() {build_model_adds_formulas_from_pipeline699,25349
+    fn build_model_extracts_date_month_component() {build_model_extracts_date_month_component718,26117
+    fn sample_wizard() -> ImportWizard {sample_wizard747,27347
+    fn wizard_starts_at_review_proposals_for_flat_array() {wizard_starts_at_review_proposals_for_flat_array756,27646
+    fn wizard_starts_at_select_array_path_for_multi_path_object() {wizard_starts_at_select_array_path_for_multi_path_object762,27815
+    fn wizard_advance_from_review_proposals_skips_dates_when_none() {wizard_advance_from_review_proposals_skips_dates_when_none772,28115
+    fn wizard_advance_full_sequence() {wizard_advance_full_sequence781,28448
+    fn wizard_move_cursor_clamps() {wizard_move_cursor_clamps796,28923
+    fn wizard_toggle_proposal() {wizard_toggle_proposal811,29356
+    fn wizard_cycle_proposal_kind() {wizard_cycle_proposal_kind821,29701
+    fn wizard_model_name_editing() {wizard_model_name_editing834,30162
+    fn wizard_confirm_path() {wizard_confirm_path845,30480
+    fn wizard_formula_lifecycle() {wizard_formula_lifecycle861,31141
+    fn wizard_delete_formula() {wizard_delete_formula899,32301
+    fn wizard_delete_formula_at_zero() {wizard_delete_formula_at_zero911,32715
+    fn wizard_confirm_empty_formula_is_noop() {wizard_confirm_empty_formula_is_noop921,33003
+    fn sample_formulas_with_two_measures() {sample_formulas_with_two_measures931,33412
+    fn sample_formulas_with_one_measure() {sample_formulas_with_one_measure941,33770
+    fn sample_formulas_with_no_measures() {sample_formulas_with_no_measures953,34178
+    fn preview_summary_for_array() {preview_summary_for_array965,34631
+    fn preview_summary_for_object() {preview_summary_for_object976,34935
+    fn wizard_date_config_toggle() {wizard_date_config_toggle990,35440
+    fn wizard_date_config_at_cursor_mapping() {wizard_date_config_at_cursor_mapping1018,36525
+    fn build_model_record_with_missing_category_value_skipped() {build_model_record_with_missing_category_value_skipped1042,37513
+    fn build_model_with_integer_category_values() {build_model_with_integer_category_values1060,38208
+    fn build_model_formulas_without_measure_category() {build_model_formulas_without_measure_category1078,39009
+    fn build_model_date_components_appear_in_cell_keys() {build_model_date_components_appear_in_cell_keys1096,39848
+
 examples/gen-grammar.rs,1493
 const GRAMMAR: &str = include_str!("../src/persistence/improv.pest");GRAMMAR24,822
 fn load_grammar() -> HashMap<String, (RuleType, Expr)> {load_grammar26,893
@@ -21,10 +2284,10 @@ impl<'g> Gen<'g> {Gen134,3547
     fn new(rules: &'g HashMap<String, (RuleType, Expr)>, seed: u64) -> Self {new135,3566
     fn pick(&mut self) -> u8 {pick142,3729
     fn try_override(&mut self, rule_name: &str, out: &mut String) -> bool {try_override147,3854
-    fn emit(&mut self, expr: &Expr, out: &mut String) {emit220,6422
-    fn generate(&mut self, rule_name: &str) -> Option<String> {generate290,9018
-fn print_rules(rules: &HashMap<String, (RuleType, Expr)>) {print_rules302,9400
-fn main() {main310,9593
+    fn emit(&mut self, expr: &Expr, out: &mut String) {emit220,6431
+    fn generate(&mut self, rule_name: &str) -> Option<String> {generate290,9021
+fn print_rules(rules: &HashMap<String, (RuleType, Expr)>) {print_rules302,9403
+fn main() {main310,9596
 
 examples/pretty-print.rs,23
 fn main() {main9,250
@@ -134,30 +2397,30 @@ pub struct GridWidget<'a> {GridWidget23,677
 impl<'a> GridWidget<'a> {GridWidget32,949
     pub fn new(new33,975
     fn render_grid(&self, area: Rect, buf: &mut Buffer) {render_grid51,1409
-impl<'a> Widget for GridWidget<'a> {GridWidget495,19504
-    fn render(self, area: Rect, buf: &mut Buffer) {render496,19541
-pub fn compute_col_widths(compute_col_widths536,20954
-pub fn compute_row_header_width(layout: &GridLayout) -> u16 {compute_row_header_width595,22937
-pub fn compute_visible_cols(compute_visible_cols623,23814
-fn truncate(s: &str, max_width: usize) -> String {truncate649,24444
-mod tests {tests672,25033
-    fn render(model: &mut Model, width: u16, height: u16) -> Buffer {render685,25604
-    fn buf_text(buf: &Buffer) -> String {buf_text702,26336
-    fn coord(pairs: &[(&str, &str)]) -> CellKey {coord717,26744
-    fn two_cat_model() -> Model {two_cat_model728,27079
-    fn column_headers_appear() {column_headers_appear752,27991
-    fn row_headers_appear() {row_headers_appear762,28477
-    fn cell_value_appears_in_correct_position() {cell_value_appears_in_correct_position772,28972
-    fn multiple_cell_values_all_appear() {multiple_cell_values_all_appear783,29328
-    fn unset_cells_show_no_value() {unset_cells_show_no_value804,30070
-    fn total_row_label_appears() {total_row_label_appears824,31022
-    fn total_row_sums_column_correctly() {total_row_sums_column_correctly831,31240
-    fn page_filter_bar_shows_category_and_selection() {page_filter_bar_shows_category_and_selection852,32016
-    fn page_filter_defaults_to_first_item() {page_filter_defaults_to_first_item876,32839
-    fn formula_cell_renders_computed_value() {formula_cell_renders_computed_value902,33812
-    fn two_row_categories_produce_cross_product_labels() {two_row_categories_produce_cross_product_labels934,35199
-    fn two_row_categories_include_all_coords_in_cell_lookup() {two_row_categories_include_all_coords_in_cell_lookup980,37011
-    fn two_column_categories_produce_cross_product_headers() {two_column_categories_produce_cross_product_headers1007,37984
+impl<'a> Widget for GridWidget<'a> {GridWidget495,19482
+    fn render(self, area: Rect, buf: &mut Buffer) {render496,19519
+pub fn compute_col_widths(compute_col_widths536,20932
+pub fn compute_row_header_width(layout: &GridLayout) -> u16 {compute_row_header_width595,22915
+pub fn compute_visible_cols(compute_visible_cols623,23792
+fn truncate(s: &str, max_width: usize) -> String {truncate649,24422
+mod tests {tests672,25011
+    fn render(model: &mut Model, width: u16, height: u16) -> Buffer {render685,25582
+    fn buf_text(buf: &Buffer) -> String {buf_text702,26314
+    fn coord(pairs: &[(&str, &str)]) -> CellKey {coord717,26722
+    fn two_cat_model() -> Model {two_cat_model728,27057
+    fn column_headers_appear() {column_headers_appear752,27969
+    fn row_headers_appear() {row_headers_appear762,28455
+    fn cell_value_appears_in_correct_position() {cell_value_appears_in_correct_position772,28950
+    fn multiple_cell_values_all_appear() {multiple_cell_values_all_appear783,29306
+    fn unset_cells_show_no_value() {unset_cells_show_no_value804,30048
+    fn total_row_label_appears() {total_row_label_appears824,31000
+    fn total_row_sums_column_correctly() {total_row_sums_column_correctly831,31218
+    fn page_filter_bar_shows_category_and_selection() {page_filter_bar_shows_category_and_selection852,31994
+    fn page_filter_defaults_to_first_item() {page_filter_defaults_to_first_item876,32817
+    fn formula_cell_renders_computed_value() {formula_cell_renders_computed_value902,33790
+    fn two_row_categories_produce_cross_product_labels() {two_row_categories_produce_cross_product_labels934,35177
+    fn two_row_categories_include_all_coords_in_cell_lookup() {two_row_categories_include_all_coords_in_cell_lookup980,36989
+    fn two_column_categories_produce_cross_product_headers() {two_column_categories_produce_cross_product_headers1007,37962
 
 src/ui/cat_tree.rs,1011
 pub enum CatTreeEntry {CatTreeEntry6,131
@@ -166,14 +2429,14 @@ pub enum CatTreeEntry {CatTreeEntry6,131
 impl CatTreeEntry {CatTreeEntry17,394
     pub fn cat_name(&self) -> &str {cat_name19,458
 pub fn build_cat_tree(model: &Model, expanded: &HashSet<String>) -> Vec<CatTreeEntry> {build_cat_tree28,713
-mod tests {tests53,1580
-    fn make_model_with_categories(cats: &[(&str, &[&str])]) -> Model {make_model_with_categories56,1611
-    fn empty_model_has_only_virtual_categories() {empty_model_has_only_virtual_categories69,1989
-    fn collapsed_category_shows_header_only() {collapsed_category_shows_header_only79,2372
-    fn expanded_category_shows_items() {expanded_category_shows_items95,2939
-    fn mixed_expanded_and_collapsed() {mixed_expanded_and_collapsed112,3662
-    fn cat_name_works_for_both_variants() {cat_name_works_for_both_variants134,4487
-    fn expanding_nonexistent_category_is_harmless() {expanding_nonexistent_category_is_harmless149,4923
+mod tests {tests52,1540
+    fn make_model_with_categories(cats: &[(&str, &[&str])]) -> Model {make_model_with_categories55,1571
+    fn empty_model_has_only_virtual_categories() {empty_model_has_only_virtual_categories68,1949
+    fn collapsed_category_shows_header_only() {collapsed_category_shows_header_only78,2332
+    fn expanded_category_shows_items() {expanded_category_shows_items94,2899
+    fn mixed_expanded_and_collapsed() {mixed_expanded_and_collapsed111,3622
+    fn cat_name_works_for_both_variants() {cat_name_works_for_both_variants133,4447
+    fn expanding_nonexistent_category_is_harmless() {expanding_nonexistent_category_is_harmless148,4883
 
 src/ui/import_wizard_ui.rs,433
 pub struct ImportWizardWidget<'a> {ImportWizardWidget11,253
@@ -184,257 +2447,263 @@ impl<'a> Widget for ImportWizardWidget<'a> {ImportWizardWidget21,444
     fn render(self, area: Rect, buf: &mut Buffer) {render22,489
 fn truncate(s: &str, max: usize) -> String {truncate339,13390
 
-src/ui/effect.rs,13895
-pub trait Effect: Debug {Effect11,253
-    fn apply(&self, app: &mut App);apply12,279
-    fn changes_mode(&self) -> bool {changes_mode14,365
-pub struct AddCategory(pub String);AddCategory22,644
-impl Effect for AddCategory {AddCategory23,680
-    fn apply(&self, app: &mut App) {apply24,710
-pub struct AddItem {AddItem30,822
-    pub category: String,category31,843
-    pub item: String,item32,869
-impl Effect for AddItem {AddItem34,893
-    fn apply(&self, app: &mut App) {apply35,919
-pub struct AddItemInGroup {AddItemInGroup45,1193
-    pub category: String,category46,1221
-    pub item: String,item47,1247
-    pub group: String,group48,1269
-impl Effect for AddItemInGroup {AddItemInGroup50,1294
-    fn apply(&self, app: &mut App) {apply51,1327
-pub struct SetCell(pub CellKey, pub CellValue);SetCell61,1623
-impl Effect for SetCell {SetCell62,1671
-    fn apply(&self, app: &mut App) {apply63,1697
-pub struct ClearCell(pub CellKey);ClearCell69,1820
-impl Effect for ClearCell {ClearCell70,1855
-    fn apply(&self, app: &mut App) {apply71,1883
-pub struct AddFormula {AddFormula77,1985
-    pub raw: String,raw78,2009
-    pub target_category: String,target_category79,2030
-impl Effect for AddFormula {AddFormula81,2065
-    fn apply(&self, app: &mut App) {apply82,2094
-pub struct RemoveFormula {RemoveFormula102,2878
-    pub target: String,target103,2905
-    pub target_category: String,target_category104,2929
-impl Effect for RemoveFormula {RemoveFormula106,2964
-    fn apply(&self, app: &mut App) {apply107,2996
-pub struct EnterEditAtCursor;EnterEditAtCursor116,3269
-impl Effect for EnterEditAtCursor {EnterEditAtCursor117,3299
-    fn apply(&self, app: &mut App) {apply118,3335
-pub struct TogglePruneEmpty;TogglePruneEmpty132,3729
-impl Effect for TogglePruneEmpty {TogglePruneEmpty133,3758
-    fn apply(&self, app: &mut App) {apply134,3793
-pub struct ToggleCatExpand(pub String);ToggleCatExpand141,3941
-impl Effect for ToggleCatExpand {ToggleCatExpand142,3981
-    fn apply(&self, app: &mut App) {apply143,4015
-pub struct RemoveItem {RemoveItem151,4190
-    pub category: String,category152,4214
-    pub item: String,item153,4240
-impl Effect for RemoveItem {RemoveItem155,4264
-    fn apply(&self, app: &mut App) {apply156,4293
-pub struct RemoveCategory(pub String);RemoveCategory162,4415
-impl Effect for RemoveCategory {RemoveCategory163,4454
-    fn apply(&self, app: &mut App) {apply164,4487
-pub struct CreateView(pub String);CreateView172,4798
-impl Effect for CreateView {CreateView173,4833
-    fn apply(&self, app: &mut App) {apply174,4862
-pub struct DeleteView(pub String);DeleteView180,4965
-impl Effect for DeleteView {DeleteView181,5000
-    fn apply(&self, app: &mut App) {apply182,5029
-pub struct SwitchView(pub String);SwitchView188,5140
-impl Effect for SwitchView {SwitchView189,5175
-    fn apply(&self, app: &mut App) {apply190,5204
-pub struct ViewBack;ViewBack202,5577
-impl Effect for ViewBack {ViewBack203,5598
-    fn apply(&self, app: &mut App) {apply204,5625
-pub struct ViewForward;ViewForward215,5991
-impl Effect for ViewForward {ViewForward216,6015
-    fn apply(&self, app: &mut App) {apply217,6045
-pub struct SetAxis {SetAxis227,6331
-    pub category: String,category228,6352
-    pub axis: Axis,axis229,6378
-impl Effect for SetAxis {SetAxis231,6400
-    fn apply(&self, app: &mut App) {apply232,6426
-pub struct SetPageSelection {SetPageSelection240,6588
-    pub category: String,category241,6618
-    pub item: String,item242,6644
-impl Effect for SetPageSelection {SetPageSelection244,6668
-    fn apply(&self, app: &mut App) {apply245,6703
-pub struct ToggleGroup {ToggleGroup253,6876
-    pub category: String,category254,6901
-    pub group: String,group255,6927
-impl Effect for ToggleGroup {ToggleGroup257,6952
-    fn apply(&self, app: &mut App) {apply258,6982
-pub struct HideItem {HideItem266,7159
-    pub category: String,category267,7181
-    pub item: String,item268,7207
-impl Effect for HideItem {HideItem270,7231
-    fn apply(&self, app: &mut App) {apply271,7258
-pub struct ShowItem {ShowItem279,7422
-    pub category: String,category280,7444
-    pub item: String,item281,7470
-impl Effect for ShowItem {ShowItem283,7494
-    fn apply(&self, app: &mut App) {apply284,7521
-pub struct TransposeAxes;TransposeAxes292,7685
-impl Effect for TransposeAxes {TransposeAxes293,7711
-    fn apply(&self, app: &mut App) {apply294,7743
-pub struct CycleAxis(pub String);CycleAxis300,7860
-impl Effect for CycleAxis {CycleAxis301,7894
-    fn apply(&self, app: &mut App) {apply302,7922
-pub struct SetNumberFormat(pub String);SetNumberFormat308,8042
-impl Effect for SetNumberFormat {SetNumberFormat309,8082
-    fn apply(&self, app: &mut App) {apply310,8116
-pub struct SetSelected(pub usize, pub usize);SetSelected318,8459
-impl Effect for SetSelected {SetSelected319,8505
-    fn apply(&self, app: &mut App) {apply320,8535
-pub struct SetRowOffset(pub usize);SetRowOffset326,8663
-impl Effect for SetRowOffset {SetRowOffset327,8699
-    fn apply(&self, app: &mut App) {apply328,8730
-pub struct SetColOffset(pub usize);SetColOffset334,8850
-impl Effect for SetColOffset {SetColOffset335,8886
-    fn apply(&self, app: &mut App) {apply336,8917
-pub struct ChangeMode(pub AppMode);ChangeMode344,9251
-impl Effect for ChangeMode {ChangeMode345,9287
-    fn apply(&self, app: &mut App) {apply346,9316
-    fn changes_mode(&self) -> bool {changes_mode349,9394
-pub struct SetStatus(pub String);SetStatus355,9470
-impl Effect for SetStatus {SetStatus356,9504
-    fn apply(&self, app: &mut App) {apply357,9532
-pub struct MarkDirty;MarkDirty363,9636
-impl Effect for MarkDirty {MarkDirty364,9658
-    fn apply(&self, app: &mut App) {apply365,9686
-pub struct SetYanked(pub Option<CellValue>);SetYanked371,9775
-impl Effect for SetYanked {SetYanked372,9820
-    fn apply(&self, app: &mut App) {apply373,9848
-pub struct SetSearchQuery(pub String);SetSearchQuery379,9948
-impl Effect for SetSearchQuery {SetSearchQuery380,9987
-    fn apply(&self, app: &mut App) {apply381,10020
-pub struct SetSearchMode(pub bool);SetSearchMode387,10126
-impl Effect for SetSearchMode {SetSearchMode388,10162
-    fn apply(&self, app: &mut App) {apply389,10194
-pub struct SetBuffer {SetBuffer396,10326
-    pub name: String,name397,10349
-    pub value: String,value398,10371
-impl Effect for SetBuffer {SetBuffer400,10396
-    fn apply(&self, app: &mut App) {apply401,10424
-pub struct SetTileCatIdx(pub usize);SetTileCatIdx412,10752
-impl Effect for SetTileCatIdx {SetTileCatIdx413,10789
-    fn apply(&self, app: &mut App) {apply414,10821
-pub struct StartDrill(pub Vec<(CellKey, CellValue)>);StartDrill422,11020
-impl Effect for StartDrill {StartDrill423,11074
-    fn apply(&self, app: &mut App) {apply424,11103
-pub struct ApplyAndClearDrill;ApplyAndClearDrill434,11418
-impl Effect for ApplyAndClearDrill {ApplyAndClearDrill435,11449
-    fn apply(&self, app: &mut App) {apply436,11486
-pub struct SetDrillPendingEdit {SetDrillPendingEdit492,13635
-    pub record_idx: usize,record_idx493,13668
-    pub col_name: String,col_name494,13695
-    pub new_value: String,new_value495,13721
-impl Effect for SetDrillPendingEdit {SetDrillPendingEdit497,13750
-    fn apply(&self, app: &mut App) {apply498,13788
-pub struct Save;Save511,14274
-impl Effect for Save {Save512,14291
-    fn apply(&self, app: &mut App) {apply513,14314
-pub struct SaveAs(pub PathBuf);SaveAs531,14877
-impl Effect for SaveAs {SaveAs532,14909
-    fn apply(&self, app: &mut App) {apply533,14934
-pub struct WizardKey {WizardKey551,15548
-    pub key_code: crossterm::event::KeyCode,key_code552,15571
-impl Effect for WizardKey {WizardKey554,15618
-    fn apply(&self, app: &mut App) {apply555,15646
-pub struct StartImportWizard(pub String);StartImportWizard681,21286
-impl Effect for StartImportWizard {StartImportWizard682,21328
-    fn apply(&self, app: &mut App) {apply683,21364
-pub struct ExportCsv(pub PathBuf);ExportCsv702,21999
-impl Effect for ExportCsv {ExportCsv703,22034
-    fn apply(&self, app: &mut App) {apply704,22062
-pub struct LoadModel(pub PathBuf);LoadModel719,22544
-impl Effect for LoadModel {LoadModel720,22579
-    fn apply(&self, app: &mut App) {apply721,22607
-pub struct ImportJsonHeadless {ImportJsonHeadless737,23116
-    pub path: PathBuf,path738,23148
-    pub model_name: Option<String>,model_name739,23171
-    pub array_path: Option<String>,array_path740,23207
-impl Effect for ImportJsonHeadless {ImportJsonHeadless742,23245
-    fn apply(&self, app: &mut App) {apply743,23282
-pub struct SetPanelOpen {SetPanelOpen847,26844
-    pub panel: Panel,panel848,26870
-    pub open: bool,open849,26892
-pub enum Panel {Panel853,26945
-    Formula,Formula854,26962
-    Category,Category855,26975
-    View,View856,26989
-impl Panel {Panel859,27002
-    pub fn mode(self) -> AppMode {mode860,27015
-impl Effect for SetPanelOpen {SetPanelOpen869,27245
-    fn apply(&self, app: &mut App) {apply870,27276
-pub struct SetPanelCursor {SetPanelCursor880,27570
-    pub panel: Panel,panel881,27598
-    pub cursor: usize,cursor882,27620
-impl Effect for SetPanelCursor {SetPanelCursor884,27645
-    fn apply(&self, app: &mut App) {apply885,27678
-pub fn mark_dirty() -> Box<dyn Effect> {mark_dirty896,28140
-pub fn set_status(msg: impl Into<String>) -> Box<dyn Effect> {set_status900,28208
-pub fn change_mode(mode: AppMode) -> Box<dyn Effect> {change_mode904,28310
-pub fn set_selected(row: usize, col: usize) -> Box<dyn Effect> {set_selected908,28399
-pub struct HelpPageNext;HelpPageNext915,28709
-impl Effect for HelpPageNext {HelpPageNext916,28734
-    fn apply(&self, app: &mut App) {apply917,28765
-pub struct HelpPagePrev;HelpPagePrev924,28964
-impl Effect for HelpPagePrev {HelpPagePrev925,28989
-    fn apply(&self, app: &mut App) {apply926,29020
-pub struct HelpPageSet(pub usize);HelpPageSet932,29140
-impl Effect for HelpPageSet {HelpPageSet933,29175
-    fn apply(&self, app: &mut App) {apply934,29205
-pub fn help_page_next() -> Box<dyn Effect> {help_page_next939,29283
-pub fn help_page_prev() -> Box<dyn Effect> {help_page_prev943,29358
-pub fn help_page_set(page: usize) -> Box<dyn Effect> {help_page_set947,29433
-mod tests {tests952,29536
-    fn test_app() -> App {test_app957,29646
-    fn add_category_effect() {add_category_effect971,30242
-    fn add_item_to_existing_category() {add_item_to_existing_category978,30442
-    fn add_item_to_nonexistent_category_sets_status() {add_item_to_nonexistent_category_sets_status996,30915
-    fn set_cell_and_clear_cell() {set_cell_and_clear_cell1007,31224
-    fn add_formula_valid() {add_formula_valid1021,31699
-    fn add_formula_adds_target_item_to_category() {add_formula_adds_target_item_to_category1035,32231
-    fn add_formula_to_measure_shows_in_effective_items() {add_formula_to_measure_shows_in_effective_items1067,33271
-    fn add_formula_invalid_sets_error_status() {add_formula_invalid_sets_error_status1093,34203
-    fn remove_formula_effect() {remove_formula_effect1104,34520
-    fn switch_view_pushes_to_back_stack() {switch_view_pushes_to_back_stack1123,35203
-    fn switch_view_to_same_does_not_push_stack() {switch_view_to_same_does_not_push_stack1136,35676
-    fn view_back_and_forward() {view_back_and_forward1143,35888
-    fn view_back_with_empty_stack_is_noop() {view_back_with_empty_stack_is_noop1163,36617
-    fn create_and_delete_view() {create_and_delete_view1171,36853
-    fn set_axis_effect() {set_axis_effect1181,37172
-    fn transpose_axes_effect() {transpose_axes_effect1192,37451
-    fn set_selected_effect() {set_selected_effect1230,38663
-    fn set_row_and_col_offset() {set_row_and_col_offset1237,38852
-    fn change_mode_effect() {change_mode_effect1248,39316
-    fn set_buffer_empty_clears() {set_buffer_empty_clears1258,39680
-    fn set_status_effect() {set_status_effect1271,40066
-    fn mark_dirty_effect() {mark_dirty_effect1278,40249
-    fn set_yanked_effect() {set_yanked_effect1286,40423
-    fn set_search_query_and_mode() {set_search_query_and_mode1293,40634
-    fn set_buffer_normal_key() {set_buffer_normal_key1306,41145
-    fn set_buffer_search_writes_to_search_query() {set_buffer_search_writes_to_search_query1317,41428
-    fn set_panel_open_and_cursor() {set_panel_open_and_cursor1331,41968
-    fn set_tile_cat_idx_effect() {set_tile_cat_idx_effect1363,42696
-    fn help_page_navigation() {help_page_navigation1372,43042
-    fn help_page_prev_clamps_at_zero() {help_page_prev_clamps_at_zero1386,43471
-    fn start_drill_and_apply_clear_drill_with_no_edits() {start_drill_and_apply_clear_drill_with_no_edits1395,43824
-    fn apply_and_clear_drill_with_value_edit() {apply_and_clear_drill_with_value_edit1412,44433
-    fn apply_and_clear_drill_with_coord_rename() {apply_and_clear_drill_with_coord_rename1439,45312
-    fn apply_and_clear_drill_empty_value_clears_cell() {apply_and_clear_drill_empty_value_clears_cell1480,46669
-    fn toggle_prune_empty_effect() {toggle_prune_empty_effect1506,47610
-    fn toggle_cat_expand_effect() {toggle_cat_expand_effect1516,47972
-    fn remove_item_and_category() {remove_item_and_category1526,48344
-    fn set_number_format_effect() {set_number_format_effect1549,49103
-    fn set_page_selection_effect() {set_page_selection_effect1558,49501
-    fn hide_and_show_item_effects() {hide_and_show_item_effects1571,49995
-    fn toggle_group_effect() {toggle_group_effect1591,50679
-    fn cycle_axis_effect() {cycle_axis_effect1618,51509
-    fn save_without_file_path_shows_status() {save_without_file_path_shows_status1629,51969
-    fn panel_mode_mapping() {panel_mode_mapping1638,52332
+src/ui/effect.rs,14353
+pub(crate) const RECORD_COORDS_CANNOT_BE_EMPTY: &str = "Record coordinates cannot be empty";RECORD_COORDS_CANNOT_BE_EMPTY9,160
+pub trait Effect: Debug {Effect13,358
+    fn apply(&self, app: &mut App);apply14,384
+    fn changes_mode(&self) -> bool {changes_mode16,470
+pub struct AddCategory(pub String);AddCategory24,749
+impl Effect for AddCategory {AddCategory25,785
+    fn apply(&self, app: &mut App) {apply26,815
+pub struct AddItem {AddItem32,927
+    pub category: String,category33,948
+    pub item: String,item34,974
+impl Effect for AddItem {AddItem36,998
+    fn apply(&self, app: &mut App) {apply37,1024
+pub struct AddItemInGroup {AddItemInGroup47,1298
+    pub category: String,category48,1326
+    pub item: String,item49,1352
+    pub group: String,group50,1374
+impl Effect for AddItemInGroup {AddItemInGroup52,1399
+    fn apply(&self, app: &mut App) {apply53,1432
+pub struct SortData;SortData63,1728
+impl Effect for SortData {SortData64,1749
+    fn apply(&self, app: &mut App) {apply65,1776
+pub struct SetCell(pub CellKey, pub CellValue);SetCell71,1877
+impl Effect for SetCell {SetCell72,1925
+    fn apply(&self, app: &mut App) {apply73,1951
+pub struct ClearCell(pub CellKey);ClearCell79,2074
+impl Effect for ClearCell {ClearCell80,2109
+    fn apply(&self, app: &mut App) {apply81,2137
+pub struct AddFormula {AddFormula87,2239
+    pub raw: String,raw88,2263
+    pub target_category: String,target_category89,2284
+impl Effect for AddFormula {AddFormula91,2319
+    fn apply(&self, app: &mut App) {apply92,2348
+pub struct RemoveFormula {RemoveFormula113,3140
+    pub target: String,target114,3167
+    pub target_category: String,target_category115,3191
+impl Effect for RemoveFormula {RemoveFormula117,3226
+    fn apply(&self, app: &mut App) {apply118,3258
+pub struct EnterEditAtCursor {EnterEditAtCursor132,3792
+    pub target_mode: AppMode,target_mode133,3823
+impl Effect for EnterEditAtCursor {EnterEditAtCursor135,3855
+    fn apply(&self, app: &mut App) {apply136,3891
+pub struct TogglePruneEmpty;TogglePruneEmpty152,4444
+impl Effect for TogglePruneEmpty {TogglePruneEmpty153,4473
+    fn apply(&self, app: &mut App) {apply154,4508
+pub struct ToggleCatExpand(pub String);ToggleCatExpand161,4656
+impl Effect for ToggleCatExpand {ToggleCatExpand162,4696
+    fn apply(&self, app: &mut App) {apply163,4730
+pub struct RemoveItem {RemoveItem171,4905
+    pub category: String,category172,4929
+    pub item: String,item173,4955
+impl Effect for RemoveItem {RemoveItem175,4979
+    fn apply(&self, app: &mut App) {apply176,5008
+pub struct RemoveCategory(pub String);RemoveCategory182,5130
+impl Effect for RemoveCategory {RemoveCategory183,5169
+    fn apply(&self, app: &mut App) {apply184,5202
+pub struct CreateView(pub String);CreateView192,5513
+impl Effect for CreateView {CreateView193,5548
+    fn apply(&self, app: &mut App) {apply194,5577
+pub struct DeleteView(pub String);DeleteView200,5680
+impl Effect for DeleteView {DeleteView201,5715
+    fn apply(&self, app: &mut App) {apply202,5744
+pub struct SwitchView(pub String);SwitchView208,5855
+impl Effect for SwitchView {SwitchView209,5890
+    fn apply(&self, app: &mut App) {apply210,5919
+pub struct ViewBack;ViewBack225,6386
+impl Effect for ViewBack {ViewBack226,6407
+    fn apply(&self, app: &mut App) {apply227,6434
+pub struct ViewForward;ViewForward242,6941
+impl Effect for ViewForward {ViewForward243,6965
+    fn apply(&self, app: &mut App) {apply244,6995
+pub struct SetAxis {SetAxis258,7422
+    pub category: String,category259,7443
+    pub axis: Axis,axis260,7469
+impl Effect for SetAxis {SetAxis262,7491
+    fn apply(&self, app: &mut App) {apply263,7517
+pub struct SetPageSelection {SetPageSelection271,7679
+    pub category: String,category272,7709
+    pub item: String,item273,7735
+impl Effect for SetPageSelection {SetPageSelection275,7759
+    fn apply(&self, app: &mut App) {apply276,7794
+pub struct ToggleGroup {ToggleGroup284,7967
+    pub category: String,category285,7992
+    pub group: String,group286,8018
+impl Effect for ToggleGroup {ToggleGroup288,8043
+    fn apply(&self, app: &mut App) {apply289,8073
+pub struct HideItem {HideItem297,8250
+    pub category: String,category298,8272
+    pub item: String,item299,8298
+impl Effect for HideItem {HideItem301,8322
+    fn apply(&self, app: &mut App) {apply302,8349
+pub struct ShowItem {ShowItem310,8513
+    pub category: String,category311,8535
+    pub item: String,item312,8561
+impl Effect for ShowItem {ShowItem314,8585
+    fn apply(&self, app: &mut App) {apply315,8612
+pub struct TransposeAxes;TransposeAxes323,8776
+impl Effect for TransposeAxes {TransposeAxes324,8802
+    fn apply(&self, app: &mut App) {apply325,8834
+pub struct CycleAxis(pub String);CycleAxis331,8951
+impl Effect for CycleAxis {CycleAxis332,8985
+    fn apply(&self, app: &mut App) {apply333,9013
+pub struct SetNumberFormat(pub String);SetNumberFormat339,9133
+impl Effect for SetNumberFormat {SetNumberFormat340,9173
+    fn apply(&self, app: &mut App) {apply341,9207
+pub struct SetSelected(pub usize, pub usize);SetSelected349,9550
+impl Effect for SetSelected {SetSelected350,9596
+    fn apply(&self, app: &mut App) {apply351,9626
+pub struct SetRowOffset(pub usize);SetRowOffset357,9754
+impl Effect for SetRowOffset {SetRowOffset358,9790
+    fn apply(&self, app: &mut App) {apply359,9821
+pub struct SetColOffset(pub usize);SetColOffset365,9941
+impl Effect for SetColOffset {SetColOffset366,9977
+    fn apply(&self, app: &mut App) {apply367,10008
+pub struct ChangeMode(pub AppMode);ChangeMode375,10342
+impl Effect for ChangeMode {ChangeMode376,10378
+    fn apply(&self, app: &mut App) {apply377,10407
+    fn changes_mode(&self) -> bool {changes_mode380,10485
+pub struct SetStatus(pub String);SetStatus386,10561
+impl Effect for SetStatus {SetStatus387,10595
+    fn apply(&self, app: &mut App) {apply388,10623
+pub struct MarkDirty;MarkDirty394,10727
+impl Effect for MarkDirty {MarkDirty395,10749
+    fn apply(&self, app: &mut App) {apply396,10777
+pub struct SetYanked(pub Option<CellValue>);SetYanked402,10866
+impl Effect for SetYanked {SetYanked403,10911
+    fn apply(&self, app: &mut App) {apply404,10939
+pub struct SetSearchQuery(pub String);SetSearchQuery410,11039
+impl Effect for SetSearchQuery {SetSearchQuery411,11078
+    fn apply(&self, app: &mut App) {apply412,11111
+pub struct SetSearchMode(pub bool);SetSearchMode418,11217
+impl Effect for SetSearchMode {SetSearchMode419,11253
+    fn apply(&self, app: &mut App) {apply420,11285
+pub struct SetBuffer {SetBuffer427,11417
+    pub name: String,name428,11440
+    pub value: String,value429,11462
+impl Effect for SetBuffer {SetBuffer431,11487
+    fn apply(&self, app: &mut App) {apply432,11515
+pub struct SetTileCatIdx(pub usize);SetTileCatIdx443,11843
+impl Effect for SetTileCatIdx {SetTileCatIdx444,11880
+    fn apply(&self, app: &mut App) {apply445,11912
+pub struct StartDrill(pub Vec<(CellKey, CellValue)>);StartDrill453,12111
+impl Effect for StartDrill {StartDrill454,12165
+    fn apply(&self, app: &mut App) {apply455,12194
+pub struct ApplyAndClearDrill;ApplyAndClearDrill465,12509
+impl Effect for ApplyAndClearDrill {ApplyAndClearDrill466,12540
+    fn apply(&self, app: &mut App) {apply467,12577
+pub struct SetDrillPendingEdit {SetDrillPendingEdit527,14896
+    pub record_idx: usize,record_idx528,14929
+    pub col_name: String,col_name529,14956
+    pub new_value: String,new_value530,14982
+impl Effect for SetDrillPendingEdit {SetDrillPendingEdit532,15011
+    fn apply(&self, app: &mut App) {apply533,15049
+pub struct Save;Save546,15535
+impl Effect for Save {Save547,15552
+    fn apply(&self, app: &mut App) {apply548,15575
+pub struct SaveAs(pub PathBuf);SaveAs566,16138
+impl Effect for SaveAs {SaveAs567,16170
+    fn apply(&self, app: &mut App) {apply568,16195
+pub struct WizardKey {WizardKey586,16809
+    pub key_code: crossterm::event::KeyCode,key_code587,16832
+impl Effect for WizardKey {WizardKey589,16879
+    fn apply(&self, app: &mut App) {apply590,16907
+pub struct StartImportWizard(pub String);StartImportWizard716,22547
+impl Effect for StartImportWizard {StartImportWizard717,22589
+    fn apply(&self, app: &mut App) {apply718,22625
+pub struct ExportCsv(pub PathBuf);ExportCsv737,23260
+impl Effect for ExportCsv {ExportCsv738,23295
+    fn apply(&self, app: &mut App) {apply739,23323
+pub struct LoadModel(pub PathBuf);LoadModel754,23805
+impl Effect for LoadModel {LoadModel755,23840
+    fn apply(&self, app: &mut App) {apply756,23868
+pub struct ImportJsonHeadless {ImportJsonHeadless772,24377
+    pub path: PathBuf,path773,24409
+    pub model_name: Option<String>,model_name774,24432
+    pub array_path: Option<String>,array_path775,24468
+impl Effect for ImportJsonHeadless {ImportJsonHeadless777,24506
+    fn apply(&self, app: &mut App) {apply778,24543
+pub struct SetPanelOpen {SetPanelOpen882,28105
+    pub panel: Panel,panel883,28131
+    pub open: bool,open884,28153
+pub enum Panel {Panel888,28206
+    Formula,Formula889,28223
+    Category,Category890,28236
+    View,View891,28250
+impl Panel {Panel894,28263
+    pub fn mode(self) -> AppMode {mode895,28276
+impl Effect for SetPanelOpen {SetPanelOpen904,28506
+    fn apply(&self, app: &mut App) {apply905,28537
+pub struct SetPanelCursor {SetPanelCursor915,28831
+    pub panel: Panel,panel916,28859
+    pub cursor: usize,cursor917,28881
+impl Effect for SetPanelCursor {SetPanelCursor919,28906
+    fn apply(&self, app: &mut App) {apply920,28939
+pub fn mark_dirty() -> Box<dyn Effect> {mark_dirty931,29401
+pub fn set_status(msg: impl Into<String>) -> Box<dyn Effect> {set_status935,29469
+pub fn change_mode(mode: AppMode) -> Box<dyn Effect> {change_mode939,29571
+pub fn set_selected(row: usize, col: usize) -> Box<dyn Effect> {set_selected943,29660
+pub struct HelpPageNext;HelpPageNext950,29970
+impl Effect for HelpPageNext {HelpPageNext951,29995
+    fn apply(&self, app: &mut App) {apply952,30026
+pub struct HelpPagePrev;HelpPagePrev959,30225
+impl Effect for HelpPagePrev {HelpPagePrev960,30250
+    fn apply(&self, app: &mut App) {apply961,30281
+pub struct HelpPageSet(pub usize);HelpPageSet967,30401
+impl Effect for HelpPageSet {HelpPageSet968,30436
+    fn apply(&self, app: &mut App) {apply969,30466
+pub fn help_page_next() -> Box<dyn Effect> {help_page_next974,30544
+pub fn help_page_prev() -> Box<dyn Effect> {help_page_prev978,30619
+pub fn help_page_set(page: usize) -> Box<dyn Effect> {help_page_set982,30694
+mod tests {tests987,30797
+    fn test_app() -> App {test_app992,30907
+    fn add_category_effect() {add_category_effect1006,31503
+    fn add_item_to_existing_category() {add_item_to_existing_category1013,31703
+    fn add_item_to_nonexistent_category_sets_status() {add_item_to_nonexistent_category_sets_status1031,32176
+    fn set_cell_and_clear_cell() {set_cell_and_clear_cell1042,32485
+    fn add_formula_valid() {add_formula_valid1056,32960
+    fn add_formula_adds_target_item_to_category() {add_formula_adds_target_item_to_category1070,33492
+    fn add_formula_to_measure_shows_in_effective_items() {add_formula_to_measure_shows_in_effective_items1102,34532
+    fn add_formula_invalid_sets_error_status() {add_formula_invalid_sets_error_status1128,35464
+    fn remove_formula_effect() {remove_formula_effect1139,35781
+    fn switch_view_pushes_to_back_stack() {switch_view_pushes_to_back_stack1158,36464
+    fn switch_view_to_same_does_not_push_stack() {switch_view_to_same_does_not_push_stack1171,36939
+    fn view_back_and_forward() {view_back_and_forward1178,37151
+    fn view_back_with_empty_stack_is_noop() {view_back_with_empty_stack_is_noop1200,37973
+    fn create_and_delete_view() {create_and_delete_view1208,38209
+    fn set_axis_effect() {set_axis_effect1218,38528
+    fn transpose_axes_effect() {transpose_axes_effect1229,38807
+    fn set_selected_effect() {set_selected_effect1267,40019
+    fn set_row_and_col_offset() {set_row_and_col_offset1274,40208
+    fn change_mode_effect() {change_mode_effect1285,40672
+    fn enter_edit_at_cursor_uses_target_mode_not_app_mode() {enter_edit_at_cursor_uses_target_mode_not_app_mode1297,41189
+    fn set_buffer_empty_clears() {set_buffer_empty_clears1330,42352
+    fn set_status_effect() {set_status_effect1343,42738
+    fn mark_dirty_effect() {mark_dirty_effect1350,42921
+    fn set_yanked_effect() {set_yanked_effect1358,43095
+    fn set_search_query_and_mode() {set_search_query_and_mode1365,43306
+    fn set_buffer_normal_key() {set_buffer_normal_key1378,43817
+    fn set_buffer_search_writes_to_search_query() {set_buffer_search_writes_to_search_query1389,44100
+    fn set_panel_open_and_cursor() {set_panel_open_and_cursor1403,44640
+    fn set_tile_cat_idx_effect() {set_tile_cat_idx_effect1435,45368
+    fn help_page_navigation() {help_page_navigation1444,45714
+    fn help_page_prev_clamps_at_zero() {help_page_prev_clamps_at_zero1458,46143
+    fn start_drill_and_apply_clear_drill_with_no_edits() {start_drill_and_apply_clear_drill_with_no_edits1467,46496
+    fn apply_and_clear_drill_with_value_edit() {apply_and_clear_drill_with_value_edit1484,47105
+    fn apply_and_clear_drill_with_coord_rename() {apply_and_clear_drill_with_coord_rename1511,47984
+    fn apply_and_clear_drill_empty_value_clears_cell() {apply_and_clear_drill_empty_value_clears_cell1552,49341
+    fn toggle_prune_empty_effect() {toggle_prune_empty_effect1578,50282
+    fn toggle_cat_expand_effect() {toggle_cat_expand_effect1588,50644
+    fn remove_item_and_category() {remove_item_and_category1598,51016
+    fn set_number_format_effect() {set_number_format_effect1621,51775
+    fn set_page_selection_effect() {set_page_selection_effect1630,52173
+    fn hide_and_show_item_effects() {hide_and_show_item_effects1643,52667
+    fn toggle_group_effect() {toggle_group_effect1663,53351
+    fn cycle_axis_effect() {cycle_axis_effect1690,54181
+    fn save_without_file_path_shows_status() {save_without_file_path_shows_status1701,54641
+    fn panel_mode_mapping() {panel_mode_mapping1710,55004
 
 src/ui/mod.rs,400
 pub mod app;app1,0
@@ -523,123 +2792,137 @@ impl<'a, C: PanelContent> Panel<'a, C> {Panel40,1432
 impl<C: PanelContent> Widget for Panel<'_, C> {Panel50,1637
     fn render(self, area: Rect, buf: &mut Buffer) {render51,1685
 
-src/ui/app.rs,6779
-pub struct DrillState {DrillState26,738
-    pub records: Rc<Vec<(crate::model::cell::CellKey, crate::model::cell::CellValue)>>,records28,845
-    pub pending_edits: std::collections::HashMap<(usize, String), String>,pending_edits31,1070
-pub struct MinibufferConfig {MinibufferConfig37,1299
-    pub buffer_key: &'static str,buffer_key38,1329
-    pub prompt: String,prompt39,1363
-    pub color: Color,color40,1387
-pub enum AppMode {AppMode44,1447
-    Normal,Normal45,1466
-    Editing {Editing46,1478
-    FormulaEdit {FormulaEdit49,1534
-    FormulaPanel,FormulaPanel52,1594
-    CategoryPanel,CategoryPanel53,1612
-    CategoryAdd {CategoryAdd55,1704
-    ItemAdd {ItemAdd59,1842
-    ViewPanel,ViewPanel63,1924
-    TileSelect,TileSelect64,1939
-    ImportWizard,ImportWizard65,1955
-    ExportPrompt {ExportPrompt66,1973
-    CommandMode {CommandMode70,2069
-    Help,Help73,2129
-    Quit,Quit74,2139
-impl AppMode {AppMode77,2152
-    pub fn minibuffer(&self) -> Option<&MinibufferConfig> {minibuffer79,2240
-    pub fn editing() -> Self {editing91,2658
-    pub fn formula_edit() -> Self {formula_edit101,2899
-    pub fn command_mode() -> Self {command_mode111,3154
-    pub fn category_add() -> Self {category_add121,3403
-    pub fn item_add(category: String) -> Self {item_add131,3666
-    pub fn export_prompt() -> Self {export_prompt143,3987
-pub struct App {App154,4251
-    pub model: Model,model155,4268
-    pub file_path: Option<PathBuf>,file_path156,4290
-    pub mode: AppMode,mode157,4326
-    pub status_msg: String,status_msg158,4349
-    pub wizard: Option<ImportWizard>,wizard159,4377
-    pub last_autosave: Instant,last_autosave160,4415
-    pub search_query: String,search_query161,4447
-    pub search_mode: bool,search_mode162,4477
-    pub formula_panel_open: bool,formula_panel_open163,4504
-    pub category_panel_open: bool,category_panel_open164,4538
-    pub view_panel_open: bool,view_panel_open165,4573
-    pub cat_panel_cursor: usize,cat_panel_cursor166,4604
-    pub view_panel_cursor: usize,view_panel_cursor167,4637
-    pub formula_cursor: usize,formula_cursor168,4671
-    pub dirty: bool,dirty169,4702
-    pub yanked: Option<CellValue>,yanked171,4763
-    pub tile_cat_idx: usize,tile_cat_idx173,4863
-    pub view_back_stack: Vec<String>,view_back_stack176,5015
-    pub view_forward_stack: Vec<String>,view_forward_stack178,5133
-    pub drill_state: Option<DrillState>,drill_state183,5455
-    pub help_page: usize,help_page185,5553
-    pub term_width: u16,term_width187,5643
-    pub term_height: u16,term_height188,5668
-    pub expanded_cats: std::collections::HashSet<String>,expanded_cats190,5755
-    pub buffers: HashMap<String, String>,buffers192,5861
-    pub transient_keymap: Option<Arc<Keymap>>,transient_keymap194,5988
-    pub layout: GridLayout,layout197,6165
-    keymap_set: KeymapSet,keymap_set198,6193
-impl App {App201,6223
-    pub fn new(mut model: Model, file_path: Option<PathBuf>) -> Self {new202,6234
-    pub fn rebuild_layout(&mut self) {rebuild_layout250,8084
-    pub fn cmd_context(&self, key: KeyCode, _mods: KeyModifiers) -> CmdContext<'_> {cmd_context265,8683
-    pub fn apply_effects(&mut self, effects: Vec<Box<dyn super::effect::Effect>>) {apply_effects327,11324
-    pub fn is_empty_model(&self) -> bool {is_empty_model336,11688
-    pub fn handle_key(&mut self, key: KeyEvent) -> Result<()> {handle_key348,12049
-    pub fn autosave_if_needed(&mut self) {autosave_if_needed376,13064
-    pub fn start_import_wizard(&mut self, json: serde_json::Value) {start_import_wizard385,13439
-    pub fn hint_text(&self) -> &'static str {hint_text391,13668
-mod tests {tests421,15079
-    fn two_col_model() -> App {two_col_model425,15139
-    fn run_cmd(app: &mut App, cmd: &dyn crate::command::cmd::Cmd) {run_cmd437,15626
-    fn enter_advance_cmd(app: &App) -> crate::command::cmd::navigation::EnterAdvance {enter_advance_cmd444,15867
-    fn enter_advance_moves_down_within_column() {enter_advance_moves_down_within_column461,16419
-    fn enter_advance_wraps_to_top_of_next_column() {enter_advance_wraps_to_top_of_next_column470,16720
-    fn enter_advance_stays_at_bottom_right() {enter_advance_stays_at_bottom_right480,17088
-    fn import_command_switches_to_import_wizard_mode() {import_command_switches_to_import_wizard_mode489,17386
-    fn execute_import_command_leaves_mode_as_import_wizard() {execute_import_command_leaves_mode_as_import_wizard502,17921
-    fn command_mode_typing_appends_to_buffer() {command_mode_typing_appends_to_buffer514,18395
-    fn col_offset_scrolls_when_cursor_moves_past_visible_columns() {col_offset_scrolls_when_cursor_moves_past_visible_columns530,19027
-    fn home_jumps_to_first_col() {home_jumps_to_first_col577,21043
-    fn end_jumps_to_last_col() {end_jumps_to_last_col586,21349
-    fn page_down_scrolls_by_three_quarters_visible() {page_down_scrolls_by_three_quarters_visible595,21652
-    fn page_up_scrolls_backward() {page_up_scrolls_backward618,22468
-    fn jump_last_row_scrolls_with_small_terminal() {jump_last_row_scrolls_with_small_terminal634,22970
-    fn ctrl_d_scrolls_viewport_with_small_terminal() {ctrl_d_scrolls_viewport_with_small_terminal660,24015
-    fn tab_in_edit_mode_commits_and_moves_right() {tab_in_edit_mode_commits_and_moves_right687,25111
-    fn command_mode_buffer_cleared_on_reentry() {command_mode_buffer_cleared_on_reentry714,26113
-    fn fresh_model_is_empty() {fresh_model_is_empty735,27114
-    fn model_with_user_category_is_not_empty() {model_with_user_category_is_not_empty744,27355
-    fn help_page_next_advances_page() {help_page_next_advances_page757,27842
-    fn help_page_prev_goes_back() {help_page_prev_goes_back768,28187
-    fn help_page_clamps_at_zero() {help_page_clamps_at_zero779,28528
-    fn help_page_clamps_at_max() {help_page_clamps_at_max790,28869
-    fn help_q_returns_to_normal() {help_q_returns_to_normal809,29527
-    fn help_esc_returns_to_normal() {help_esc_returns_to_normal822,29895
-    fn help_colon_enters_command_mode() {help_colon_enters_command_mode835,30261
-    fn add_item_to_nonexistent_category_sets_status() {add_item_to_nonexistent_category_sets_status851,30859
-    fn add_formula_with_bad_syntax_sets_status() {add_formula_with_bad_syntax_sets_status867,31374
-    fn tile_axis_change_stays_in_tile_select() {tile_axis_change_stays_in_tile_select885,32045
-    fn category_panel_colon_enters_command_mode() {category_panel_colon_enters_command_mode907,32862
-    fn view_panel_colon_enters_command_mode() {view_panel_colon_enters_command_mode921,33300
-    fn tile_select_colon_enters_command_mode() {tile_select_colon_enters_command_mode935,33726
+src/ui/app.rs,7956
+pub struct ViewFrame {ViewFrame25,683
+    pub view_name: String,view_name26,706
+    pub mode: AppMode,mode27,733
+pub struct DrillState {DrillState33,902
+    pub records: Rc<Vec<(crate::model::cell::CellKey, crate::model::cell::CellValue)>>,records35,1009
+    pub pending_edits: std::collections::HashMap<(usize, String), String>,pending_edits38,1234
+pub struct MinibufferConfig {MinibufferConfig44,1463
+    pub buffer_key: &'static str,buffer_key45,1493
+    pub prompt: String,prompt46,1527
+    pub color: Color,color47,1551
+pub enum AppMode {AppMode51,1611
+    Normal,Normal52,1630
+    Editing {Editing53,1642
+    FormulaEdit {FormulaEdit56,1698
+    FormulaPanel,FormulaPanel59,1758
+    CategoryPanel,CategoryPanel60,1776
+    CategoryAdd {CategoryAdd62,1868
+    ItemAdd {ItemAdd66,2006
+    ViewPanel,ViewPanel70,2088
+    TileSelect,TileSelect71,2103
+    ImportWizard,ImportWizard72,2119
+    ExportPrompt {ExportPrompt73,2137
+    CommandMode {CommandMode77,2233
+    Help,Help80,2293
+    Quit,Quit81,2303
+    RecordsNormal,RecordsNormal83,2395
+    RecordsEditing {RecordsEditing85,2497
+impl AppMode {AppMode90,2563
+    pub fn minibuffer(&self) -> Option<&MinibufferConfig> {minibuffer92,2651
+    pub fn is_editing(&self) -> bool {is_editing106,3180
+    pub fn editing() -> Self {editing110,3301
+    pub fn records_editing() -> Self {records_editing120,3542
+    pub fn is_records(&self) -> bool {is_records131,3853
+    pub fn formula_edit() -> Self {formula_edit135,3973
+    pub fn command_mode() -> Self {command_mode145,4228
+    pub fn category_add() -> Self {category_add155,4477
+    pub fn item_add(category: String) -> Self {item_add165,4740
+    pub fn export_prompt() -> Self {export_prompt177,5061
+pub struct App {App188,5325
+    pub model: Model,model189,5342
+    pub file_path: Option<PathBuf>,file_path190,5364
+    pub mode: AppMode,mode191,5400
+    pub status_msg: String,status_msg192,5423
+    pub wizard: Option<ImportWizard>,wizard193,5451
+    pub last_autosave: Instant,last_autosave194,5489
+    pub search_query: String,search_query195,5521
+    pub search_mode: bool,search_mode196,5551
+    pub formula_panel_open: bool,formula_panel_open197,5578
+    pub category_panel_open: bool,category_panel_open198,5612
+    pub view_panel_open: bool,view_panel_open199,5647
+    pub cat_panel_cursor: usize,cat_panel_cursor200,5678
+    pub view_panel_cursor: usize,view_panel_cursor201,5711
+    pub formula_cursor: usize,formula_cursor202,5745
+    pub dirty: bool,dirty203,5776
+    pub yanked: Option<CellValue>,yanked205,5837
+    pub tile_cat_idx: usize,tile_cat_idx207,5937
+    pub view_back_stack: Vec<ViewFrame>,view_back_stack210,6089
+    pub view_forward_stack: Vec<ViewFrame>,view_forward_stack212,6210
+    pub drill_state: Option<DrillState>,drill_state217,6535
+    pub help_page: usize,help_page219,6633
+    pub term_width: u16,term_width221,6723
+    pub term_height: u16,term_height222,6748
+    pub expanded_cats: std::collections::HashSet<String>,expanded_cats224,6835
+    pub buffers: HashMap<String, String>,buffers226,6941
+    pub transient_keymap: Option<Arc<Keymap>>,transient_keymap228,7068
+    pub layout: GridLayout,layout231,7245
+    keymap_set: KeymapSet,keymap_set232,7273
+impl App {App235,7303
+    pub fn new(mut model: Model, file_path: Option<PathBuf>) -> Self {new236,7314
+    pub fn rebuild_layout(&mut self) {rebuild_layout284,9164
+    pub fn cmd_context(&self, key: KeyCode, _mods: KeyModifiers) -> CmdContext<'_> {cmd_context299,9763
+    pub fn apply_effects(&mut self, effects: Vec<Box<dyn super::effect::Effect>>) {apply_effects362,12461
+    pub fn is_empty_model(&self) -> bool {is_empty_model371,12825
+    pub fn handle_key(&mut self, key: KeyEvent) -> Result<()> {handle_key383,13186
+    pub fn autosave_if_needed(&mut self) {autosave_if_needed411,14201
+    pub fn start_import_wizard(&mut self, json: serde_json::Value) {start_import_wizard422,14580
+    pub fn hint_text(&self) -> &'static str {hint_text428,14809
+mod tests {tests463,16417
+    fn two_col_model() -> App {two_col_model467,16477
+    fn run_cmd(app: &mut App, cmd: &dyn crate::command::cmd::Cmd) {run_cmd479,16964
+    fn enter_advance_cmd(app: &App) -> crate::command::cmd::navigation::EnterAdvance {enter_advance_cmd486,17205
+    fn enter_advance_moves_down_within_column() {enter_advance_moves_down_within_column503,17757
+    fn enter_advance_wraps_to_top_of_next_column() {enter_advance_wraps_to_top_of_next_column512,18058
+    fn enter_advance_stays_at_bottom_right() {enter_advance_stays_at_bottom_right522,18426
+    fn import_command_switches_to_import_wizard_mode() {import_command_switches_to_import_wizard_mode531,18724
+    fn execute_import_command_leaves_mode_as_import_wizard() {execute_import_command_leaves_mode_as_import_wizard544,19259
+    fn command_mode_typing_appends_to_buffer() {command_mode_typing_appends_to_buffer556,19733
+    fn col_offset_scrolls_when_cursor_moves_past_visible_columns() {col_offset_scrolls_when_cursor_moves_past_visible_columns572,20365
+    fn home_jumps_to_first_col() {home_jumps_to_first_col619,22381
+    fn end_jumps_to_last_col() {end_jumps_to_last_col628,22687
+    fn page_down_scrolls_by_three_quarters_visible() {page_down_scrolls_by_three_quarters_visible637,22990
+    fn page_up_scrolls_backward() {page_up_scrolls_backward660,23805
+    fn jump_last_row_scrolls_with_small_terminal() {jump_last_row_scrolls_with_small_terminal676,24306
+    fn ctrl_d_scrolls_viewport_with_small_terminal() {ctrl_d_scrolls_viewport_with_small_terminal702,25350
+    fn tab_in_edit_mode_commits_and_moves_right() {tab_in_edit_mode_commits_and_moves_right729,26445
+    fn entering_records_mode_sorts_existing_data() {entering_records_mode_sorts_existing_data759,27674
+    fn add_record_row_in_empty_records_view_creates_first_row() {add_record_row_in_empty_records_view_creates_first_row795,29115
+    fn edit_record_row_in_blank_model_persists_value() {edit_record_row_in_blank_model_persists_value826,30356
+    fn records_model_with_two_rows() -> App {records_model_with_two_rows865,32017
+    fn tab_on_bottom_right_of_records_inserts_below() {tab_on_bottom_right_of_records_inserts_below920,33962
+    fn drill_edit_is_staged_until_view_back() {drill_edit_is_staged_until_view_back958,35393
+    fn blanking_records_category_does_not_create_empty_item() {blanking_records_category_does_not_create_empty_item1017,37806
+    fn command_mode_buffer_cleared_on_reentry() {command_mode_buffer_cleared_on_reentry1047,38887
+    fn fresh_model_is_empty() {fresh_model_is_empty1068,39888
+    fn model_with_user_category_is_not_empty() {model_with_user_category_is_not_empty1077,40129
+    fn help_page_next_advances_page() {help_page_next_advances_page1090,40616
+    fn help_page_prev_goes_back() {help_page_prev_goes_back1101,40961
+    fn help_page_clamps_at_zero() {help_page_clamps_at_zero1112,41302
+    fn help_page_clamps_at_max() {help_page_clamps_at_max1123,41643
+    fn help_q_returns_to_normal() {help_q_returns_to_normal1142,42301
+    fn help_esc_returns_to_normal() {help_esc_returns_to_normal1155,42669
+    fn help_colon_enters_command_mode() {help_colon_enters_command_mode1168,43035
+    fn add_item_to_nonexistent_category_sets_status() {add_item_to_nonexistent_category_sets_status1184,43633
+    fn add_formula_with_bad_syntax_sets_status() {add_formula_with_bad_syntax_sets_status1200,44148
+    fn tile_axis_change_stays_in_tile_select() {tile_axis_change_stays_in_tile_select1218,44819
+    fn category_panel_colon_enters_command_mode() {category_panel_colon_enters_command_mode1240,45636
+    fn view_panel_colon_enters_command_mode() {view_panel_colon_enters_command_mode1254,46074
+    fn tile_select_colon_enters_command_mode() {tile_select_colon_enters_command_mode1268,46500
 
-src/lib.rs,249
+src/lib.rs,221
 pub mod command;command1,0
 pub mod draw;draw2,17
 pub mod format;format3,31
-pub mod formula;formula4,47
-pub mod import;import5,64
-pub mod model;model6,80
-pub mod persistence;persistence7,95
-pub mod ui;ui8,116
-pub mod view;view9,128
+pub mod import;import5,85
+pub mod model;model6,101
+pub mod persistence;persistence7,116
+pub mod ui;ui8,137
+pub mod view;view9,149
 
-src/draw.rs,1274
+src/draw.rs,1275
 struct TuiContext<'a> {TuiContext31,881
     terminal: Terminal<CrosstermBackend<&'a mut Stdout>>,terminal32,905
 impl<'a> TuiContext<'a> {TuiContext35,966
@@ -651,182 +2934,72 @@ fn fill_line(left: String, right: &str, width: u16) -> String {fill_line96,265
 fn centered_popup(area: Rect, width: u16, height: u16) -> Rect {centered_popup101,2842
 fn draw_popup_frame(f: &mut Frame, popup: Rect, title: &str, border_color: Color) -> Rect {draw_popup_frame109,3119
 fn mode_name(mode: &AppMode) -> &'static str {mode_name120,3474
-fn mode_style(mode: &AppMode) -> Style {mode_style139,4163
-fn draw(f: &mut Frame, app: &App) {draw148,4562
-fn draw_title(f: &mut Frame, area: Rect, app: &App) {draw_title186,5907
-fn draw_content(f: &mut Frame, area: Rect, app: &App) {draw_content209,6613
-fn draw_tile_bar(f: &mut Frame, area: Rect, app: &App) {draw_tile_bar268,8484
-fn draw_bottom_bar(f: &mut Frame, area: Rect, app: &App) {draw_bottom_bar272,8626
-fn draw_status(f: &mut Frame, area: Rect, app: &App) {draw_status294,9251
-fn draw_welcome(f: &mut Frame, area: Rect) {draw_welcome316,9932
-
-src/formula/mod.rs,49
-pub mod ast;ast1,0
-pub mod parser;parser2,13
-
-src/formula/ast.rs,1076
-pub enum AggFunc {AggFunc4,86
-    Sum,Sum5,105
-    Avg,Avg6,114
-    Min,Min7,123
-    Max,Max8,132
-    Count,Count9,141
-pub enum BinOp {BinOp17,488
-    Add,Add18,505
-    Sub,Sub19,514
-    Mul,Mul20,523
-    Div,Div21,532
-    Pow,Pow22,541
-    Eq,Eq23,550
-    Ne,Ne24,558
-    Lt,Lt25,566
-    Gt,Gt26,574
-    Le,Le27,582
-    Ge,Ge28,590
-pub struct Filter {Filter32,649
-    pub category: String,category33,669
-    pub item: String,item34,695
-pub enum Expr {Expr38,768
-    Number(f64),Number39,784
-    Ref(String),Ref40,801
-    BinOp(BinOp, Box<Expr>, Box<Expr>),BinOp41,818
-    UnaryMinus(Box<Expr>),UnaryMinus42,858
-    Agg(AggFunc, Box<Expr>, Option<Filter>),Agg43,885
-    If(Box<Expr>, Box<Expr>, Box<Expr>),If44,930
-pub struct Formula {Formula48,1022
-    pub raw: String,raw50,1104
-    pub target: String,target52,1194
-    pub target_category: String,target_category54,1266
-    pub expr: Expr,expr56,1334
-    pub filter: Option<Filter>,filter58,1384
-impl Formula {Formula61,1419
-    pub fn new(new62,1434
-
-src/formula/parser.rs,4182
-pub fn parse_formula(raw: &str, target_category: &str) -> Result<Formula> {parse_formula7,201
-fn split_where(s: &str) -> (&str, Option<&str>) {split_where26,820
-fn unquote(s: &str) -> String {unquote64,1950
-fn parse_where(s: &str) -> Result<Filter> {parse_where73,2180
-pub fn parse_expr(s: &str) -> Result<Expr> {parse_expr84,2576
-enum Token {Token98,2928
-    Number(f64),Number99,2941
-    Ident(String),Ident100,2958
-    Str(String),Str101,2977
-    Plus,Plus102,2994
-    Minus,Minus103,3004
-    Star,Star104,3015
-    Slash,Slash105,3025
-    Caret,Caret106,3036
-    LParen,LParen107,3047
-    RParen,RParen108,3059
-    Comma,Comma109,3071
-    Eq,Eq110,3082
-    Ne,Ne111,3090
-    Lt,Lt112,3098
-    Gt,Gt113,3106
-    Le,Le114,3114
-    Ge,Ge115,3122
-fn tokenize(s: &str) -> Result<Vec<Token>> {tokenize118,3133
-fn parse_add_sub(tokens: &[Token], pos: &mut usize) -> Result<Expr> {parse_add_sub277,8753
-fn parse_mul_div(tokens: &[Token], pos: &mut usize) -> Result<Expr> {parse_mul_div292,9211
-fn parse_pow(tokens: &[Token], pos: &mut usize) -> Result<Expr> {parse_pow307,9661
-fn parse_unary(tokens: &[Token], pos: &mut usize) -> Result<Expr> {parse_unary317,9991
-fn parse_primary(tokens: &[Token], pos: &mut usize) -> Result<Expr> {parse_primary326,10274
-fn parse_comparison(tokens: &[Token], pos: &mut usize) -> Result<Expr> {parse_comparison441,15475
-mod tests {tests461,16050
-    fn parse_simple_subtraction() {parse_simple_subtraction466,16153
-    fn parse_where_clause() {parse_where_clause474,16434
-    fn parse_sum_aggregation() {parse_sum_aggregation483,16753
-    fn parse_avg_aggregation() {parse_avg_aggregation489,16942
-    fn parse_if_expression() {parse_if_expression495,17129
-    fn parse_numeric_literal() {parse_numeric_literal501,17326
-    fn parse_chained_arithmetic() {parse_chained_arithmetic507,17519
-    fn parse_missing_equals_returns_error() {parse_missing_equals_returns_error512,17638
-    fn parse_min_aggregation() {parse_min_aggregation519,17949
-    fn parse_max_aggregation() {parse_max_aggregation525,18135
-    fn parse_count_aggregation() {parse_count_aggregation531,18321
-    fn parse_sum_with_top_level_where_works() {parse_sum_with_top_level_where_works539,18667
-    fn parse_sum_with_inline_where_filter() {parse_sum_with_inline_where_filter550,19190
-    fn parse_if_with_comparison_operators() {parse_if_with_comparison_operators564,19852
-    fn parse_where_with_quoted_string_inside_expression() {parse_where_with_quoted_string_inside_expression585,20774
-    fn parse_power_operator() {parse_power_operator595,21275
-    fn parse_unary_minus() {parse_unary_minus603,21640
-    fn parse_multiplication() {parse_multiplication611,21964
-    fn parse_division() {parse_division617,22152
-    fn parse_nested_parens() {parse_nested_parens625,22493
-    fn parse_aggregate_name_without_parens_is_ref() {parse_aggregate_name_without_parens_is_ref633,22788
-    fn parse_if_without_parens_is_ref() {parse_if_without_parens_is_ref643,23182
-    fn parse_quoted_string_in_where() {parse_quoted_string_in_where656,23725
-    fn parse_unexpected_token_error() {parse_unexpected_token_error666,24205
-    fn parse_unexpected_character_error() {parse_unexpected_character_error673,24393
-    fn parse_empty_expression_error() {parse_empty_expression_error679,24537
-    fn tokenizer_breaks_at_where_keyword() {tokenizer_breaks_at_where_keyword685,24669
-    fn parse_multi_word_identifier() {parse_multi_word_identifier695,25152
-    fn split_where_ignores_where_inside_quotes() {split_where_ignores_where_inside_quotes703,25485
-    fn pipe_quoted_identifier_in_expression() {pipe_quoted_identifier_in_expression713,25962
-    fn pipe_quoted_keyword_as_identifier() {pipe_quoted_keyword_as_identifier725,26463
-    fn pipe_quoted_identifier_with_special_chars() {pipe_quoted_identifier_with_special_chars737,26947
-    fn pipe_quoted_in_aggregate() {pipe_quoted_in_aggregate749,27477
-    fn pipe_quoted_in_where_filter_value() {pipe_quoted_in_where_filter_value759,27841
-    fn pipe_quoted_in_inline_where() {pipe_quoted_in_inline_where766,28091
+fn mode_style(mode: &AppMode) -> Style {mode_style141,4268
+fn draw(f: &mut Frame, app: &App) {draw152,4723
+fn draw_title(f: &mut Frame, area: Rect, app: &App) {draw_title191,6064
+fn draw_content(f: &mut Frame, area: Rect, app: &App) {draw_content214,6770
+fn draw_tile_bar(f: &mut Frame, area: Rect, app: &App) {draw_tile_bar273,8641
+fn draw_bottom_bar(f: &mut Frame, area: Rect, app: &App) {draw_bottom_bar277,8783
+fn draw_status(f: &mut Frame, area: Rect, app: &App) {draw_status299,9408
+fn draw_welcome(f: &mut Frame, area: Rect) {draw_welcome321,10089
 
 src/persistence/mod.rs,4159
 struct ImprovParser;ImprovParser18,466
 fn is_bare_name(name: &str) -> bool {is_bare_name23,707
 fn escape_pipe(s: &str) -> String {escape_pipe33,1040
-    fn text_value_is_empty_string() {text_value_is_empty_string1290,46916
-    fn text_value_with_newline() {text_value_with_newline1306,47424
-    fn text_value_looks_like_number() {text_value_looks_like_number1322,47956
-    fn text_value_with_hash_prefix() {text_value_with_hash_prefix1338,48488
-    fn item_name_with_brackets_misinterpreted_as_group() {item_name_with_brackets_misinterpreted_as_group1354,49030
-    fn category_name_with_comma_space_in_data() {category_name_with_comma_space_in_data1373,49788
-    fn item_name_with_equals_sign_in_data() {item_name_with_equals_sign_in_data1394,50558
-    fn view_name_with_parentheses() {view_name_with_parentheses1415,51280
-    fn multiple_tricky_text_cells() {multiple_tricky_text_cells1431,51797
-mod parser_prop_tests {parser_prop_tests1468,53153
-    fn coord(pairs: &[(&str, &str)]) -> CellKey {coord1474,53325
-    fn safe_ident() -> impl Strategy<Value = String> {safe_ident1484,53606
-    fn tricky_text() -> impl Strategy<Value = String> {tricky_text1489,53764
-    fn cell_value() -> impl Strategy<Value = CellValue> {cell_value1506,54317
-    fn arbitrary_model() -> impl Strategy<Value = Model> {arbitrary_model1516,54677
-mod parser_edge_cases {parser_edge_cases1736,63478
-    fn coord(pairs: &[(&str, &str)]) -> CellKey {coord1742,63659
-    fn backtick_in_category_name() {backtick_in_category_name1754,64039
-    fn item_name_with_both_equals_and_comma() {item_name_with_both_equals_and_comma1775,64738
-    fn hidden_item_with_slash_in_name() {hidden_item_with_slash_in_name1798,65621
-    fn collapsed_group_with_slash_in_name() {collapsed_group_with_slash_in_name1815,66239
-    fn view_name_ending_with_active_string() {view_name_ending_with_active_string1829,66703
-    fn group_name_with_brackets() {group_name_with_brackets1848,67484
-    fn item_in_group_where_item_has_brackets() {item_in_group_where_item_has_brackets1871,68294
-    fn parse_empty_string() {parse_empty_string1898,69358
-    fn parse_just_model_name() {parse_just_model_name1907,69603
-    fn parse_data_without_value() {parse_data_without_value1913,69757
-    fn parse_data_with_empty_coords() {parse_data_with_empty_coords1920,69989
-    fn parse_duplicate_categories() {parse_duplicate_categories1927,70223
-    fn parse_category_with_no_items() {parse_category_with_no_items1936,70592
-    fn number_negative_zero_roundtrips() {number_negative_zero_roundtrips1947,71110
-    fn number_very_large_roundtrips() {number_very_large_roundtrips1965,71788
-    fn number_very_small_roundtrips() {number_very_small_roundtrips1987,72512
-    fn number_pi_roundtrips() {number_pi_roundtrips2009,73242
-    fn model_name_with_leading_trailing_spaces() {model_name_with_leading_trailing_spaces2033,74115
-    fn category_name_with_trailing_spaces() {category_name_with_trailing_spaces2041,74406
-    fn data_line_with_extra_whitespace() {data_line_with_extra_whitespace2049,74714
-    fn three_categories_round_trip() {three_categories_round_trip2059,75254
-    fn text_value_with_backslash() {text_value_with_backslash2089,76386
-    fn text_value_with_backslash_n_literal() {text_value_with_backslash_n_literal2110,77096
-mod generator {generator2138,78240
-    fn load_grammar() -> HashMap<String, (RuleType, Expr)> {load_grammar2145,78454
-    struct Gen<'g> {Gen2161,79179
-        rules: &'g HashMap<String, (RuleType, Expr)>,rules2162,79200
-        choices: Vec<u8>,choices2163,79254
-        pos: usize,pos2164,79280
-    impl<'g> Gen<'g> {Gen2167,79307
-        fn new(rules: &'g HashMap<String, (RuleType, Expr)>, choices: Vec<u8>) -> Self {new2168,79330
-        fn pick(&mut self) -> u8 {pick2177,79593
-        fn emit(&mut self, expr: &Expr, out: &mut String) {emit2183,79750
-        fn generate(&mut self, rule_name: &str) -> String {generate2259,82798
-    pub fn improv_file() -> impl Strategy<Value = String> {improv_file2269,83149
-mod grammar_prop_tests {grammar_prop_tests2280,83509
+    fn text_value_is_empty_string() {text_value_is_empty_string1294,46932
+    fn text_value_with_newline() {text_value_with_newline1310,47440
+    fn text_value_looks_like_number() {text_value_looks_like_number1326,47972
+    fn text_value_with_hash_prefix() {text_value_with_hash_prefix1342,48504
+    fn item_name_with_brackets_misinterpreted_as_group() {item_name_with_brackets_misinterpreted_as_group1358,49046
+    fn category_name_with_comma_space_in_data() {category_name_with_comma_space_in_data1377,49804
+    fn item_name_with_equals_sign_in_data() {item_name_with_equals_sign_in_data1398,50574
+    fn view_name_with_parentheses() {view_name_with_parentheses1419,51296
+    fn multiple_tricky_text_cells() {multiple_tricky_text_cells1435,51813
+mod parser_prop_tests {parser_prop_tests1472,53169
+    fn coord(pairs: &[(&str, &str)]) -> CellKey {coord1478,53341
+    fn safe_ident() -> impl Strategy<Value = String> {safe_ident1488,53622
+    fn tricky_text() -> impl Strategy<Value = String> {tricky_text1493,53780
+    fn cell_value() -> impl Strategy<Value = CellValue> {cell_value1510,54333
+    fn arbitrary_model() -> impl Strategy<Value = Model> {arbitrary_model1520,54693
+mod parser_edge_cases {parser_edge_cases1740,63494
+    fn coord(pairs: &[(&str, &str)]) -> CellKey {coord1746,63675
+    fn backtick_in_category_name() {backtick_in_category_name1758,64055
+    fn item_name_with_both_equals_and_comma() {item_name_with_both_equals_and_comma1779,64754
+    fn hidden_item_with_slash_in_name() {hidden_item_with_slash_in_name1802,65637
+    fn collapsed_group_with_slash_in_name() {collapsed_group_with_slash_in_name1819,66255
+    fn view_name_ending_with_active_string() {view_name_ending_with_active_string1833,66719
+    fn group_name_with_brackets() {group_name_with_brackets1852,67500
+    fn item_in_group_where_item_has_brackets() {item_in_group_where_item_has_brackets1875,68310
+    fn parse_empty_string() {parse_empty_string1902,69374
+    fn parse_just_model_name() {parse_just_model_name1911,69619
+    fn parse_data_without_value() {parse_data_without_value1917,69773
+    fn parse_data_with_empty_coords() {parse_data_with_empty_coords1924,70005
+    fn parse_duplicate_categories() {parse_duplicate_categories1931,70239
+    fn parse_category_with_no_items() {parse_category_with_no_items1940,70608
+    fn number_negative_zero_roundtrips() {number_negative_zero_roundtrips1951,71126
+    fn number_very_large_roundtrips() {number_very_large_roundtrips1969,71804
+    fn number_very_small_roundtrips() {number_very_small_roundtrips1991,72528
+    fn number_pi_roundtrips() {number_pi_roundtrips2013,73258
+    fn model_name_with_leading_trailing_spaces() {model_name_with_leading_trailing_spaces2037,74131
+    fn category_name_with_trailing_spaces() {category_name_with_trailing_spaces2045,74422
+    fn data_line_with_extra_whitespace() {data_line_with_extra_whitespace2053,74730
+    fn three_categories_round_trip() {three_categories_round_trip2063,75270
+    fn text_value_with_backslash() {text_value_with_backslash2093,76402
+    fn text_value_with_backslash_n_literal() {text_value_with_backslash_n_literal2114,77112
+mod generator {generator2142,78256
+    fn load_grammar() -> HashMap<String, (RuleType, Expr)> {load_grammar2149,78470
+    struct Gen<'g> {Gen2165,79195
+        rules: &'g HashMap<String, (RuleType, Expr)>,rules2166,79216
+        choices: Vec<u8>,choices2167,79270
+        pos: usize,pos2168,79296
+    impl<'g> Gen<'g> {Gen2171,79323
+        fn new(rules: &'g HashMap<String, (RuleType, Expr)>, choices: Vec<u8>) -> Self {new2172,79346
+        fn pick(&mut self) -> u8 {pick2181,79609
+        fn emit(&mut self, expr: &Expr, out: &mut String) {emit2187,79766
+        fn generate(&mut self, rule_name: &str) -> String {generate2263,82824
+    pub fn improv_file() -> impl Strategy<Value = String> {improv_file2273,83175
+mod grammar_prop_tests {grammar_prop_tests2284,83535
 
 src/model/symbol.rs,879
 pub struct Symbol(u64);Symbol5,171
@@ -845,7 +3018,7 @@ mod tests {tests54,1534
     fn different_strings_different_ids() {different_strings_different_ids66,1766
     fn resolve_roundtrips() {resolve_roundtrips74,1964
 
-src/model/types.rs,10437
+src/model/types.rs,10549
 const MAX_CATEGORIES: usize = 12;MAX_CATEGORIES12,283
 pub struct Model {Model15,366
     pub name: String,name16,385
@@ -859,184 +3032,186 @@ pub struct Model {Model15,366
     formula_cache: HashMap<CellKey, CellValue>,formula_cache29,926
 impl Model {Model32,977
     pub fn new(name: impl Into<String>) -> Self {new33,990
-    pub fn add_category(&mut self, name: impl Into<String>) -> Result<CategoryId> {add_category77,2687
-    pub fn add_label_category(&mut self, name: impl Into<String>) -> Result<CategoryId> {add_label_category105,3777
-    pub fn remove_category(&mut self, name: &str) {remove_category124,4524
-    pub fn remove_item(&mut self, cat_name: &str, item_name: &str) {remove_item148,5347
-    pub fn category_mut(&mut self, name: &str) -> Option<&mut Category> {category_mut163,5814
-    pub fn category(&self, name: &str) -> Option<&Category> {category167,5933
-    pub fn set_cell(&mut self, key: CellKey, value: CellValue) {set_cell171,6036
-    pub fn clear_cell(&mut self, key: &CellKey) {clear_cell175,6143
-    pub fn get_cell(&self, key: &CellKey) -> Option<&CellValue> {get_cell179,6231
-    pub fn add_formula(&mut self, formula: Formula) {add_formula183,6331
-    pub fn remove_formula(&mut self, target: &str, target_category: &str) {remove_formula201,7115
-    pub fn formulas(&self) -> &[Formula] {formulas206,7308
-    pub fn active_view(&self) -> &View {active_view210,7381
-    pub fn active_view_mut(&mut self) -> &mut View {active_view_mut216,7549
-    pub fn create_view(&mut self, name: impl Into<String>) -> &mut View {create_view222,7733
-    pub fn switch_view(&mut self, name: &str) -> Result<()> {switch_view233,8146
-    pub fn delete_view(&mut self, name: &str) -> Result<()> {delete_view242,8405
-    pub fn normalize_view_state(&mut self) {normalize_view_state256,8930
-    pub fn category_names(&self) -> Vec<&str> {category_names265,9196
-    pub fn measure_item_names(&self) -> Vec<String> {measure_item_names273,9593
-    pub fn effective_item_names(&self, cat_name: &str) -> Vec<String> {effective_item_names294,10325
-    pub fn regular_category_names(&self) -> Vec<&str> {regular_category_names310,10850
-    const MAX_EVAL_DEPTH: u8 = 16;MAX_EVAL_DEPTH322,11344
-    pub fn evaluate(&self, key: &CellKey) -> Option<CellValue> {evaluate324,11380
-    fn evaluate_depth(&self, key: &CellKey, depth: u8) -> Option<CellValue> {evaluate_depth328,11507
-    pub fn evaluate_aggregated(&self, key: &CellKey, none_cats: &[String]) -> Option<CellValue> evaluate_aggregated345,12257
-    pub fn evaluate_aggregated_f64(&self, key: &CellKey, none_cats: &[String]) -> f64 {evaluate_aggregated_f64384,13586
-    pub fn recompute_formulas(&mut self, none_cats: &[String]) {recompute_formulas398,14232
-    fn evaluate_formula_cell(&self, key: &CellKey, none_cats: &[String]) -> Option<CellValue> {evaluate_formula_cell456,16547
-    fn eval_formula_with_cache(eval_formula_with_cache468,17058
-        fn find_item_category<'a>(model: &'a Model, item_name: &str) -> Option<&'a str> {find_item_category487,17627
-        fn eval_expr_cached(eval_expr_cached503,18256
-        fn eval_bool_cached(eval_bool_cached608,23043
-    fn aggregate_raw(&self, key: &CellKey, none_cats: &[String]) -> Option<CellValue> {aggregate_raw647,24430
-    fn eval_formula_depth(eval_formula_depth674,25406
-        fn find_item_category<'a>(model: &'a Model, item_name: &str) -> Option<&'a str> {find_item_category693,25947
-        fn eval_expr(eval_expr712,26781
-        fn eval_bool(eval_bool801,30819
-mod model_tests {model_tests843,32220
-    fn coord(pairs: &[(&str, &str)]) -> CellKey {coord848,32338
-    fn new_model_has_default_view() {new_model_has_default_view858,32571
-    fn add_category_creates_entry() {add_category_creates_entry865,32781
-    fn add_category_duplicate_is_idempotent() {add_category_duplicate_is_idempotent872,32970
-    fn add_category_max_limit() {add_category_max_limit882,33318
-    fn add_category_notifies_existing_views() {add_category_notifies_existing_views891,33555
-    fn set_and_get_cell_roundtrip() {set_and_get_cell_roundtrip899,33852
-    fn get_unset_cell_returns_empty() {get_unset_cell_returns_empty909,34234
-    fn overwrite_cell() {overwrite_cell916,34417
-    fn three_category_model_independent_cells() {three_category_model_independent_cells925,34725
-    fn remove_category_deletes_category_and_cells() {remove_category_deletes_category_and_cells961,36040
-    fn create_view_copies_category_structure() {create_view_copies_category_structure986,37012
-    fn switch_view_changes_active_view() {switch_view_changes_active_view998,37469
-    fn switch_view_unknown_returns_error() {switch_view_unknown_returns_error1006,37688
-    fn delete_view_removes_it() {delete_view_removes_it1012,37847
-    fn delete_last_view_returns_error() {delete_last_view_returns_error1020,38062
-    fn delete_active_view_switches_to_another() {delete_active_view_switches_to_another1026,38215
-    fn first_category_goes_to_row_second_to_column_rest_to_page() {first_category_goes_to_row_second_to_column_rest_to_page1035,38482
-    fn data_is_shared_across_views() {data_is_shared_across_views1047,38929
-    fn evaluate_aggregated_sums_over_hidden_dimension() {evaluate_aggregated_sums_over_hidden_dimension1056,39230
-    fn measure_item_names_includes_formula_targets() {measure_item_names_includes_formula_targets1097,40789
-    fn evaluate_aggregated_no_hidden_delegates_to_evaluate() {evaluate_aggregated_no_hidden_delegates_to_evaluate1128,41900
-    fn evaluate_aggregated_respects_measure_agg() {evaluate_aggregated_respects_measure_agg1141,42367
-mod formula_tests {formula_tests1172,43506
-    fn coord(pairs: &[(&str, &str)]) -> CellKey {coord1177,43638
-    fn approx_eq(a: f64, b: f64) -> bool {approx_eq1186,43859
-    fn revenue_cost_model() -> Model {revenue_cost_model1190,43938
-    fn profit_equals_revenue_minus_cost() {profit_equals_revenue_minus_cost1223,44978
-    fn formula_evaluates_per_region() {formula_evaluates_per_region1231,45307
-    fn formula_multiplication() {formula_multiplication1241,45782
-    fn formula_division() {formula_division1255,46253
-    fn division_by_zero_yields_empty() {division_by_zero_yields_empty1271,46850
-    fn unary_minus() {unary_minus1297,47802
-    fn power_operator() {power_operator1308,48220
-    fn formula_with_missing_ref_returns_empty() {formula_with_missing_ref_returns_empty1324,48777
-    fn circular_formula_returns_error() {circular_formula_returns_error1340,49408
-    fn self_referencing_formula_returns_error() {self_referencing_formula_returns_error1359,50118
-    fn formula_where_applied_to_matching_region() {formula_where_applied_to_matching_region1375,50673
-    fn formula_where_not_applied_to_non_matching_region() {formula_where_not_applied_to_non_matching_region1391,51219
-    fn add_formula_replaces_same_target() {add_formula_replaces_same_target1406,51701
-    fn remove_formula() {remove_formula1416,52163
-    fn sum_aggregation_across_region() {sum_aggregation_across_region1426,52536
-    fn count_aggregation() {count_aggregation1441,53077
-    fn if_true_branch() {if_true_branch1464,53868
-    fn if_false_branch() {if_false_branch1480,54423
-    fn where_filter_absent_category_does_not_apply_formula() {where_filter_absent_category_does_not_apply_formula1505,55663
-    fn sum_inner_expression_constrains_which_cells_are_summed() {sum_inner_expression_constrains_which_cells_are_summed1530,56928
-    fn add_formula_same_target_name_different_category_both_coexist() {add_formula_same_target_name_different_category_both_coexist1564,58308
-    fn add_formula_same_target_name_different_category_evaluates_independently() {add_formula_same_target_name_different_category_evaluates_independently1586,59187
-    fn formula_chain_preserves_full_precision() {formula_chain_preserves_full_precision1618,60420
-    fn remove_formula_only_removes_specified_target_category() {remove_formula_only_removes_specified_target_category1667,62470
-mod five_category {five_category1695,63384
-    const DATA: &[(&str, &str, &str, &str, f64, f64)] = &[DATA1701,63543
-    fn coord(region: &str, product: &str, channel: &str, time: &str, measure: &str) -> CellKey {coord1720,64534
-    fn build_model() -> Model {build_model1730,64959
-    fn approx(a: f64, b: f64) -> bool {approx1770,66494
-    fn all_sixteen_revenue_cells_stored() {all_sixteen_revenue_cells_stored1775,66582
-    fn all_sixteen_cost_cells_stored() {all_sixteen_cost_cells_stored1785,66871
-    fn spot_check_raw_revenue() {spot_check_raw_revenue1795,67154
-    fn distinct_cells_do_not_alias() {distinct_cells_do_not_alias1808,67543
-    fn profit_formula_correct_at_every_intersection() {profit_formula_correct_at_every_intersection1820,67887
-    fn margin_formula_correct_at_every_intersection() {margin_formula_correct_at_every_intersection1836,68530
-    fn chained_formula_profit_feeds_margin() {chained_formula_profit_feeds_margin1852,69187
-    fn update_revenue_updates_profit_and_margin() {update_revenue_updates_profit_and_margin1862,69510
-    fn sum_revenue_for_east_region() {sum_revenue_for_east_region1881,70205
-    fn sum_revenue_for_online_channel() {sum_revenue_for_online_channel1897,70783
-    fn sum_revenue_for_shirts_q1() {sum_revenue_for_shirts_q11913,71371
-    fn sum_all_revenue_equals_grand_total() {sum_all_revenue_equals_grand_total1930,72017
-    fn default_view_first_two_on_axes_rest_on_page() {default_view_first_two_on_axes_rest_on_page1939,72428
-    fn rearranging_axes_does_not_affect_data() {rearranging_axes_does_not_affect_data1950,72834
-    fn two_views_have_independent_axis_assignments() {two_views_have_independent_axis_assignments1967,73385
-    fn page_selections_are_per_view() {page_selections_are_per_view1987,74112
-    fn five_categories_well_within_limit() {five_categories_well_within_limit2004,74606
-mod prop_tests {prop_tests2019,75118
-    fn finite_f64() -> impl Strategy<Value = f64> {finite_f642025,75277
+    pub fn add_category(&mut self, name: impl Into<String>) -> Result<CategoryId> {add_category73,2445
+    pub fn add_label_category(&mut self, name: impl Into<String>) -> Result<CategoryId> {add_label_category101,3535
+    pub fn remove_category(&mut self, name: &str) {remove_category120,4282
+    pub fn remove_item(&mut self, cat_name: &str, item_name: &str) {remove_item144,5105
+    pub fn category_mut(&mut self, name: &str) -> Option<&mut Category> {category_mut159,5572
+    pub fn category(&self, name: &str) -> Option<&Category> {category163,5691
+    pub fn set_cell(&mut self, key: CellKey, value: CellValue) {set_cell167,5794
+    pub fn clear_cell(&mut self, key: &CellKey) {clear_cell171,5901
+    pub fn get_cell(&self, key: &CellKey) -> Option<&CellValue> {get_cell175,5989
+    pub fn add_formula(&mut self, formula: Formula) {add_formula179,6089
+    pub fn remove_formula(&mut self, target: &str, target_category: &str) {remove_formula198,6873
+    pub fn formulas(&self) -> &[Formula] {formulas203,7066
+    pub fn active_view(&self) -> &View {active_view207,7139
+    pub fn active_view_mut(&mut self) -> &mut View {active_view_mut213,7307
+    pub fn create_view(&mut self, name: impl Into<String>) -> &mut View {create_view219,7491
+    pub fn switch_view(&mut self, name: &str) -> Result<()> {switch_view230,7904
+    pub fn delete_view(&mut self, name: &str) -> Result<()> {delete_view239,8163
+    pub fn normalize_view_state(&mut self) {normalize_view_state253,8688
+    pub fn category_names(&self) -> Vec<&str> {category_names262,8954
+    pub fn measure_item_names(&self) -> Vec<String> {measure_item_names270,9351
+    pub fn effective_item_names(&self, cat_name: &str) -> Vec<String> {effective_item_names291,10083
+    pub fn regular_category_names(&self) -> Vec<&str> {regular_category_names307,10608
+    const MAX_EVAL_DEPTH: u8 = 16;MAX_EVAL_DEPTH319,11102
+    pub fn evaluate(&self, key: &CellKey) -> Option<CellValue> {evaluate321,11138
+    fn evaluate_depth(&self, key: &CellKey, depth: u8) -> Option<CellValue> {evaluate_depth325,11265
+    pub fn evaluate_aggregated(&self, key: &CellKey, none_cats: &[String]) -> Option<CellValue> evaluate_aggregated343,12019
+    pub fn evaluate_aggregated_f64(&self, key: &CellKey, none_cats: &[String]) -> f64 {evaluate_aggregated_f64382,13348
+    pub fn recompute_formulas(&mut self, none_cats: &[String]) {recompute_formulas396,13994
+    fn evaluate_formula_cell(&self, key: &CellKey, none_cats: &[String]) -> Option<CellValue> {evaluate_formula_cell454,16309
+    fn eval_formula_with_cache(eval_formula_with_cache467,16824
+        fn find_item_category<'a>(model: &'a Model, item_name: &str) -> Option<&'a str> {find_item_category486,17393
+        fn eval_expr_cached(eval_expr_cached502,18022
+        fn eval_bool_cached(eval_bool_cached608,22821
+    fn aggregate_raw(&self, key: &CellKey, none_cats: &[String]) -> Option<CellValue> {aggregate_raw647,24208
+    fn eval_formula_depth(eval_formula_depth674,25184
+        fn find_item_category<'a>(model: &'a Model, item_name: &str) -> Option<&'a str> {find_item_category693,25725
+        fn eval_expr(eval_expr712,26559
+        fn eval_bool(eval_bool802,30609
+mod model_tests {model_tests844,32010
+    fn coord(pairs: &[(&str, &str)]) -> CellKey {coord849,32128
+    fn new_model_has_default_view() {new_model_has_default_view859,32361
+    fn new_model_registers_virtuals_in_default_view() {new_model_registers_virtuals_in_default_view868,32735
+    fn add_category_creates_entry() {add_category_creates_entry877,33038
+    fn add_category_duplicate_is_idempotent() {add_category_duplicate_is_idempotent884,33227
+    fn add_category_max_limit() {add_category_max_limit894,33575
+    fn add_category_notifies_existing_views() {add_category_notifies_existing_views903,33812
+    fn set_and_get_cell_roundtrip() {set_and_get_cell_roundtrip911,34109
+    fn get_unset_cell_returns_empty() {get_unset_cell_returns_empty921,34491
+    fn overwrite_cell() {overwrite_cell928,34674
+    fn three_category_model_independent_cells() {three_category_model_independent_cells937,34982
+    fn remove_category_deletes_category_and_cells() {remove_category_deletes_category_and_cells973,36297
+    fn create_view_copies_category_structure() {create_view_copies_category_structure998,37269
+    fn switch_view_changes_active_view() {switch_view_changes_active_view1010,37726
+    fn switch_view_unknown_returns_error() {switch_view_unknown_returns_error1018,37945
+    fn delete_view_removes_it() {delete_view_removes_it1024,38104
+    fn delete_last_view_returns_error() {delete_last_view_returns_error1032,38319
+    fn delete_active_view_switches_to_another() {delete_active_view_switches_to_another1038,38472
+    fn first_category_goes_to_row_second_to_column_rest_to_page() {first_category_goes_to_row_second_to_column_rest_to_page1047,38739
+    fn data_is_shared_across_views() {data_is_shared_across_views1059,39186
+    fn evaluate_aggregated_sums_over_hidden_dimension() {evaluate_aggregated_sums_over_hidden_dimension1068,39487
+    fn measure_item_names_includes_formula_targets() {measure_item_names_includes_formula_targets1109,41046
+    fn evaluate_aggregated_no_hidden_delegates_to_evaluate() {evaluate_aggregated_no_hidden_delegates_to_evaluate1140,42157
+    fn evaluate_aggregated_respects_measure_agg() {evaluate_aggregated_respects_measure_agg1153,42624
+mod formula_tests {formula_tests1184,43763
+    fn coord(pairs: &[(&str, &str)]) -> CellKey {coord1189,43895
+    fn approx_eq(a: f64, b: f64) -> bool {approx_eq1198,44116
+    fn revenue_cost_model() -> Model {revenue_cost_model1202,44195
+    fn profit_equals_revenue_minus_cost() {profit_equals_revenue_minus_cost1235,45235
+    fn formula_evaluates_per_region() {formula_evaluates_per_region1243,45564
+    fn formula_multiplication() {formula_multiplication1253,46039
+    fn formula_division() {formula_division1267,46510
+    fn division_by_zero_yields_empty() {division_by_zero_yields_empty1283,47107
+    fn unary_minus() {unary_minus1309,48059
+    fn power_operator() {power_operator1320,48477
+    fn formula_with_missing_ref_returns_empty() {formula_with_missing_ref_returns_empty1336,49034
+    fn circular_formula_returns_error() {circular_formula_returns_error1352,49665
+    fn self_referencing_formula_returns_error() {self_referencing_formula_returns_error1371,50375
+    fn formula_where_applied_to_matching_region() {formula_where_applied_to_matching_region1387,50930
+    fn formula_where_not_applied_to_non_matching_region() {formula_where_not_applied_to_non_matching_region1403,51476
+    fn add_formula_replaces_same_target() {add_formula_replaces_same_target1418,51958
+    fn remove_formula() {remove_formula1428,52420
+    fn sum_aggregation_across_region() {sum_aggregation_across_region1438,52793
+    fn count_aggregation() {count_aggregation1453,53334
+    fn if_true_branch() {if_true_branch1476,54125
+    fn if_false_branch() {if_false_branch1492,54680
+    fn where_filter_absent_category_does_not_apply_formula() {where_filter_absent_category_does_not_apply_formula1517,55920
+    fn sum_inner_expression_constrains_which_cells_are_summed() {sum_inner_expression_constrains_which_cells_are_summed1542,57185
+    fn add_formula_same_target_name_different_category_both_coexist() {add_formula_same_target_name_different_category_both_coexist1576,58565
+    fn add_formula_same_target_name_different_category_evaluates_independently() {add_formula_same_target_name_different_category_evaluates_independently1598,59444
+    fn formula_chain_preserves_full_precision() {formula_chain_preserves_full_precision1630,60677
+    fn remove_formula_only_removes_specified_target_category() {remove_formula_only_removes_specified_target_category1679,62727
+mod five_category {five_category1707,63641
+    const DATA: &[(&str, &str, &str, &str, f64, f64)] = &[DATA1713,63800
+    fn coord(region: &str, product: &str, channel: &str, time: &str, measure: &str) -> CellKey {coord1732,64791
+    fn build_model() -> Model {build_model1742,65216
+    fn approx(a: f64, b: f64) -> bool {approx1782,66751
+    fn all_sixteen_revenue_cells_stored() {all_sixteen_revenue_cells_stored1787,66839
+    fn all_sixteen_cost_cells_stored() {all_sixteen_cost_cells_stored1797,67127
+    fn spot_check_raw_revenue() {spot_check_raw_revenue1807,67409
+    fn distinct_cells_do_not_alias() {distinct_cells_do_not_alias1820,67798
+    fn profit_formula_correct_at_every_intersection() {profit_formula_correct_at_every_intersection1828,68074
+    fn margin_formula_correct_at_every_intersection() {margin_formula_correct_at_every_intersection1844,68717
+    fn chained_formula_profit_feeds_margin() {chained_formula_profit_feeds_margin1860,69374
+    fn update_revenue_updates_profit_and_margin() {update_revenue_updates_profit_and_margin1870,69697
+    fn sum_revenue_for_east_region() {sum_revenue_for_east_region1889,70392
+    fn sum_revenue_for_online_channel() {sum_revenue_for_online_channel1905,70970
+    fn sum_revenue_for_shirts_q1() {sum_revenue_for_shirts_q11921,71558
+    fn sum_all_revenue_equals_grand_total() {sum_all_revenue_equals_grand_total1938,72204
+    fn default_view_first_two_on_axes_rest_on_page() {default_view_first_two_on_axes_rest_on_page1947,72615
+    fn rearranging_axes_does_not_affect_data() {rearranging_axes_does_not_affect_data1958,73021
+    fn two_views_have_independent_axis_assignments() {two_views_have_independent_axis_assignments1975,73572
+    fn page_selections_are_per_view() {page_selections_are_per_view1995,74299
+    fn five_categories_well_within_limit() {five_categories_well_within_limit2012,74793
+mod prop_tests {prop_tests2027,75305
+    fn finite_f64() -> impl Strategy<Value = f64> {finite_f642033,75464
 
-src/model/cell.rs,4317
-pub struct CellKey(pub Vec<(String, String)>);CellKey9,311
-impl CellKey {CellKey11,359
-    pub fn new(mut coords: Vec<(String, String)>) -> Self {new12,374
-    pub fn get(&self, category: &str) -> Option<&str> {get17,508
-    pub fn with(mut self, category: impl Into<String>, item: impl Into<String>) -> Self {with24,686
-    pub fn without(&self, category: &str) -> Self {without36,1081
-    pub fn matches_partial(&self, partial: &[(String, String)]) -> bool {matches_partial47,1333
-impl std::fmt::Display for CellKey {CellKey54,1521
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {fmt55,1558
-pub enum CellValue {CellValue62,1831
-    Number(f64),Number63,1852
-    Text(String),Text64,1869
-    Error(String),Error66,1955
-impl CellValue {CellValue69,1977
-    pub fn as_f64(&self) -> Option<f64> {as_f6470,1994
-    pub fn is_error(&self) -> bool {is_error77,2143
-impl std::fmt::Display for CellValue {CellValue82,2233
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {fmt83,2272
-pub struct InternedKey(pub Vec<(Symbol, Symbol)>);InternedKey101,2919
-pub struct DataStore {DataStore106,3152
-    cells: HashMap<InternedKey, CellValue>,cells108,3240
-    pub symbols: SymbolTable,symbols110,3355
-    index: HashMap<(Symbol, Symbol), HashSet<InternedKey>>,index112,3462
-impl Serialize for DataStore {DataStore115,3525
-    fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {serialize116,3556
-impl<'de> Deserialize<'de> for DataStore {DataStore127,3912
-    fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {deserialize128,3955
-impl DataStore {DataStore138,4260
-    pub fn new() -> Self {new139,4277
-    pub fn intern_key(&mut self, key: &CellKey) -> InternedKey {intern_key144,4381
-    pub fn to_cell_key(&self, ikey: &InternedKey) -> CellKey {to_cell_key149,4573
-    pub fn set(&mut self, key: CellKey, value: CellValue) {set163,4960
-    pub fn get(&self, key: &CellKey) -> Option<&CellValue> {get172,5269
-    fn lookup_key(&self, key: &CellKey) -> Option<InternedKey> {lookup_key178,5485
-    pub fn iter_cells(&self) -> impl Iterator<Item = (CellKey, &CellValue)> {iter_cells188,5850
-    pub fn remove(&mut self, key: &CellKey) {remove192,6000
-    pub fn matching_values(&self, partial: &[(String, String)]) -> Vec<&CellValue> {matching_values207,6496
-    pub fn matching_cells(&self, partial: &[(String, String)]) -> Vec<(CellKey, &CellValue)> {matching_cells246,7752
-mod cell_key {cell_key285,8872
-    fn key(pairs: &[(&str, &str)]) -> CellKey {key288,8912
-    fn coords_are_sorted_by_category_name() {coords_are_sorted_by_category_name298,9143
-    fn get_returns_item_for_known_category() {get_returns_item_for_known_category310,9468
-    fn get_returns_none_for_unknown_category() {get_returns_none_for_unknown_category317,9706
-    fn with_adds_new_coordinate_in_sorted_order() {with_adds_new_coordinate_in_sorted_order323,9862
-    fn with_replaces_existing_coordinate() {with_replaces_existing_coordinate332,10191
-    fn without_removes_coordinate() {without_removes_coordinate339,10430
-    fn without_missing_category_is_noop() {without_missing_category_is_noop347,10703
-    fn matches_partial_full_match() {matches_partial_full_match353,10863
-    fn matches_partial_empty_matches_all() {matches_partial_empty_matches_all360,11105
-    fn matches_partial_wrong_item_no_match() {matches_partial_wrong_item_no_match366,11277
-    fn matches_partial_missing_category_no_match() {matches_partial_missing_category_no_match373,11529
-    fn display_format() {display_format380,11767
-mod data_store {data_store387,11911
-    fn key(pairs: &[(&str, &str)]) -> CellKey {key390,11977
-    fn get_missing_returns_empty() {get_missing_returns_empty400,12208
-    fn set_and_get_roundtrip() {set_and_get_roundtrip406,12368
-    fn overwrite_value() {overwrite_value414,12651
-    fn remove_evicts_key() {remove_evicts_key423,12959
-    fn matching_cells_returns_correct_subset() {matching_cells_returns_correct_subset432,13227
-mod prop_tests {prop_tests456,14067
-    fn pairs_map() -> impl Strategy<Value = Vec<(String, String)>> {pairs_map461,14248
-    fn finite_f64() -> impl Strategy<Value = f64> {finite_f64467,14496
+src/model/cell.rs,4379
+pub struct CellKey(pub Vec<(String, String)>);CellKey10,335
+impl CellKey {CellKey12,383
+    pub fn new(mut coords: Vec<(String, String)>) -> Self {new13,398
+    pub fn get(&self, category: &str) -> Option<&str> {get18,532
+    pub fn with(mut self, category: impl Into<String>, item: impl Into<String>) -> Self {with25,710
+    pub fn without(&self, category: &str) -> Self {without37,1105
+    pub fn matches_partial(&self, partial: &[(String, String)]) -> bool {matches_partial48,1357
+impl std::fmt::Display for CellKey {CellKey55,1545
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {fmt56,1582
+pub enum CellValue {CellValue63,1855
+    Number(f64),Number64,1876
+    Text(String),Text65,1893
+    Error(String),Error67,1979
+impl CellValue {CellValue70,2001
+    pub fn as_f64(&self) -> Option<f64> {as_f6471,2018
+    pub fn is_error(&self) -> bool {is_error78,2167
+impl std::fmt::Display for CellValue {CellValue83,2257
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {fmt84,2296
+pub struct InternedKey(pub Vec<(Symbol, Symbol)>);InternedKey102,2943
+pub struct DataStore {DataStore107,3176
+    cells: IndexMap<InternedKey, CellValue>,cells110,3333
+    pub symbols: SymbolTable,symbols112,3449
+    index: HashMap<(Symbol, Symbol), HashSet<InternedKey>>,index114,3556
+impl Serialize for DataStore {DataStore117,3619
+    fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {serialize118,3650
+impl<'de> Deserialize<'de> for DataStore {DataStore129,4006
+    fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {deserialize130,4049
+impl DataStore {DataStore140,4354
+    pub fn new() -> Self {new141,4371
+    pub fn intern_key(&mut self, key: &CellKey) -> InternedKey {intern_key146,4475
+    pub fn to_cell_key(&self, ikey: &InternedKey) -> CellKey {to_cell_key151,4667
+    pub fn sort_by_key(&mut self) {sort_by_key168,5243
+    pub fn set(&mut self, key: CellKey, value: CellValue) {set185,5794
+    pub fn get(&self, key: &CellKey) -> Option<&CellValue> {get194,6103
+    fn lookup_key(&self, key: &CellKey) -> Option<InternedKey> {lookup_key200,6319
+    pub fn iter_cells(&self) -> impl Iterator<Item = (CellKey, &CellValue)> {iter_cells210,6684
+    pub fn remove(&mut self, key: &CellKey) {remove214,6834
+    pub fn matching_values(&self, partial: &[(String, String)]) -> Vec<&CellValue> {matching_values229,7336
+    pub fn matching_cells(&self, partial: &[(String, String)]) -> Vec<(CellKey, &CellValue)> {matching_cells268,8592
+mod cell_key {cell_key307,9712
+    fn key(pairs: &[(&str, &str)]) -> CellKey {key310,9752
+    fn coords_are_sorted_by_category_name() {coords_are_sorted_by_category_name320,9983
+    fn get_returns_item_for_known_category() {get_returns_item_for_known_category332,10308
+    fn get_returns_none_for_unknown_category() {get_returns_none_for_unknown_category339,10546
+    fn with_adds_new_coordinate_in_sorted_order() {with_adds_new_coordinate_in_sorted_order345,10702
+    fn with_replaces_existing_coordinate() {with_replaces_existing_coordinate354,11031
+    fn without_removes_coordinate() {without_removes_coordinate361,11270
+    fn without_missing_category_is_noop() {without_missing_category_is_noop369,11543
+    fn matches_partial_full_match() {matches_partial_full_match375,11703
+    fn matches_partial_empty_matches_all() {matches_partial_empty_matches_all382,11945
+    fn matches_partial_wrong_item_no_match() {matches_partial_wrong_item_no_match388,12117
+    fn matches_partial_missing_category_no_match() {matches_partial_missing_category_no_match395,12369
+    fn display_format() {display_format402,12607
+mod data_store {data_store409,12751
+    fn key(pairs: &[(&str, &str)]) -> CellKey {key412,12817
+    fn get_missing_returns_empty() {get_missing_returns_empty422,13048
+    fn set_and_get_roundtrip() {set_and_get_roundtrip428,13208
+    fn overwrite_value() {overwrite_value436,13491
+    fn remove_evicts_key() {remove_evicts_key445,13799
+    fn matching_cells_returns_correct_subset() {matching_cells_returns_correct_subset454,14067
+mod prop_tests {prop_tests478,14907
+    fn pairs_map() -> impl Strategy<Value = Vec<(String, String)>> {pairs_map483,15088
+    fn finite_f64() -> impl Strategy<Value = f64> {finite_f64489,15336
 
 src/model/mod.rs,109
 pub mod category;category1,0
@@ -1145,66 +3320,69 @@ mod tests {tests217,7212
 mod prop_tests {prop_tests384,12404
     fn unique_cat_names() -> impl Strategy<Value = Vec<String>> {unique_cat_names389,12500
 
-src/view/layout.rs,4726
-pub fn synthetic_record_info(key: &CellKey) -> Option<(usize, String)> {synthetic_record_info9,242
-pub enum AxisEntry {AxisEntry21,739
-    GroupHeader {GroupHeader22,760
-    DataItem(Vec<String>),DataItem26,839
-pub struct GridLayout {GridLayout34,1187
-    pub row_cats: Vec<String>,row_cats35,1211
-    pub col_cats: Vec<String>,col_cats36,1242
-    pub page_coords: Vec<(String, String)>,page_coords37,1273
-    pub row_items: Vec<AxisEntry>,row_items38,1317
-    pub col_items: Vec<AxisEntry>,col_items39,1352
-    pub none_cats: Vec<String>,none_cats41,1457
-    pub records: Option<Rc<Vec<(CellKey, CellValue)>>>,records44,1610
-impl GridLayout {GridLayout47,1669
-    pub fn with_frozen_records(with_frozen_records50,1833
-    pub fn new(model: &Model, view: &View) -> Self {new70,2504
-    fn build_records_mode(build_records_mode128,4479
-    pub fn records_display(&self, row: usize, col: usize) -> Option<String> {records_display184,6393
-    pub fn prune_empty(&mut self, model: &Model) {prune_empty209,7362
-    pub fn is_records_mode(&self) -> bool {is_records_mode301,10722
-    pub fn row_count(&self) -> usize {row_count306,10858
-    pub fn col_count(&self) -> usize {col_count314,11086
-    pub fn row_label(&self, row: usize) -> String {row_label321,11257
-    pub fn col_label(&self, col: usize) -> String {col_label336,11641
-    pub fn resolve_display(&self, key: &CellKey) -> Option<String> {resolve_display353,12151
-    pub fn display_text(display_text367,12753
-    pub fn cell_key(&self, row: usize, col: usize) -> Option<CellKey> {cell_key388,13537
-    pub fn data_row_to_visual(&self, data_row: usize) -> Option<usize> {data_row_to_visual435,15049
-    pub fn data_col_to_visual(&self, data_col: usize) -> Option<usize> {data_col_to_visual449,15500
-    pub fn row_group_for(&self, data_row: usize) -> Option<(String, String)> {row_group_for464,16012
-    pub fn col_group_for(&self, data_col: usize) -> Option<(String, String)> {col_group_for481,16600
-fn expand_category(expand_category500,17249
-fn cross_product(model: &Model, view: &View, cats: &[String]) -> Vec<AxisEntry> {cross_product550,18870
-mod tests {tests568,19464
-    fn records_model() -> Model {records_model574,19646
-    fn prune_empty_removes_all_empty_columns_in_pivot_mode() {prune_empty_removes_all_empty_columns_in_pivot_mode599,20462
-    fn records_mode_activated_when_index_and_dim_on_axes() {records_mode_activated_when_index_and_dim_on_axes620,21304
-    fn records_mode_cell_key_returns_synthetic_for_all_columns() {records_mode_cell_key_returns_synthetic_for_all_columns631,21697
-    fn records_mode_resolve_display_returns_values() {records_mode_resolve_display_returns_values654,22680
-    fn synthetic_record_info_returns_none_for_pivot_keys() {synthetic_record_info_returns_none_for_pivot_keys681,23765
-    fn synthetic_record_info_extracts_index_and_dim() {synthetic_record_info_extracts_index_and_dim690,24064
-    fn coord(pairs: &[(&str, &str)]) -> CellKey {coord700,24411
-    fn two_cat_model() -> Model {two_cat_model709,24632
-    fn row_and_col_counts_match_item_counts() {row_and_col_counts_match_item_counts723,25036
-    fn cell_key_encodes_correct_coordinates() {cell_key_encodes_correct_coordinates731,25311
-    fn cell_key_out_of_bounds_returns_none() {cell_key_out_of_bounds_returns_none740,25628
-    fn cell_key_includes_page_coords() {cell_key_includes_page_coords748,25888
-    fn cell_key_round_trips_through_model_evaluate() {cell_key_round_trips_through_model_evaluate764,26575
-    fn labels_join_with_slash_for_multi_cat_axis() {labels_join_with_slash_for_multi_cat_axis777,27035
-    fn row_count_excludes_group_headers() {row_count_excludes_group_headers792,27640
-    fn group_header_emitted_at_group_boundary() {group_header_emitted_at_group_boundary811,28321
-    fn collapsed_group_has_header_but_no_data_items() {collapsed_group_has_header_but_no_data_items838,29289
-    fn ungrouped_items_produce_no_headers() {ungrouped_items_produce_no_headers869,30497
-    fn cell_key_correct_with_grouped_items() {cell_key_correct_with_grouped_items887,30990
-    fn data_row_to_visual_skips_headers() {data_row_to_visual_skips_headers909,31782
-    fn data_col_to_visual_skips_headers() {data_col_to_visual_skips_headers928,32594
-    fn row_group_for_finds_enclosing_group() {row_group_for_finds_enclosing_group947,33370
-    fn row_group_for_returns_none_for_ungrouped() {row_group_for_returns_none_for_ungrouped970,34123
-    fn col_group_for_finds_enclosing_group() {col_group_for_finds_enclosing_group981,34540
-    fn col_group_for_returns_none_for_ungrouped() {col_group_for_returns_none_for_ungrouped1004,35310
+src/view/layout.rs,5045
+pub fn synthetic_record_info(key: &CellKey) -> Option<(usize, String)> {synthetic_record_info10,284
+pub enum AxisEntry {AxisEntry22,781
+    GroupHeader {GroupHeader23,802
+    DataItem(Vec<String>),DataItem27,881
+pub struct GridLayout {GridLayout35,1229
+    pub row_cats: Vec<String>,row_cats36,1253
+    pub col_cats: Vec<String>,col_cats37,1284
+    pub page_coords: Vec<(String, String)>,page_coords38,1315
+    pub row_items: Vec<AxisEntry>,row_items39,1359
+    pub col_items: Vec<AxisEntry>,col_items40,1394
+    pub none_cats: Vec<String>,none_cats42,1499
+    pub records: Option<Rc<Vec<(CellKey, CellValue)>>>,records45,1652
+impl GridLayout {GridLayout48,1711
+    pub fn with_frozen_records(with_frozen_records51,1875
+    pub fn new(model: &Model, view: &View) -> Self {new72,2530
+    fn build_records_mode(build_records_mode129,4478
+    pub fn records_display(&self, row: usize, col: usize) -> Option<String> {records_display186,6448
+    pub fn prune_empty(&mut self, model: &Model) {prune_empty211,7417
+    pub fn is_records_mode(&self) -> bool {is_records_mode303,10777
+    pub fn row_count(&self) -> usize {row_count308,10913
+    pub fn col_count(&self) -> usize {col_count316,11141
+    pub fn row_label(&self, row: usize) -> String {row_label323,11312
+    pub fn col_label(&self, col: usize) -> String {col_label338,11696
+    pub fn resolve_display(&self, key: &CellKey) -> Option<String> {resolve_display355,12206
+    pub fn display_text(display_text369,12808
+    pub fn cell_key(&self, row: usize, col: usize) -> Option<CellKey> {cell_key390,13592
+    pub fn data_row_to_visual(&self, data_row: usize) -> Option<usize> {data_row_to_visual437,15104
+    pub fn data_col_to_visual(&self, data_col: usize) -> Option<usize> {data_col_to_visual451,15555
+    pub fn row_group_for(&self, data_row: usize) -> Option<(String, String)> {row_group_for466,16067
+    pub fn col_group_for(&self, data_col: usize) -> Option<(String, String)> {col_group_for483,16655
+fn expand_category(expand_category502,17304
+fn cross_product(model: &Model, view: &View, cats: &[String]) -> Vec<AxisEntry> {cross_product552,18925
+mod tests {tests570,19519
+    fn records_model() -> Model {records_model576,19701
+    fn prune_empty_removes_all_empty_columns_in_pivot_mode() {prune_empty_removes_all_empty_columns_in_pivot_mode603,20637
+    fn records_mode_activated_when_index_and_dim_on_axes() {records_mode_activated_when_index_and_dim_on_axes624,21479
+    fn records_mode_cell_key_returns_synthetic_for_all_columns() {records_mode_cell_key_returns_synthetic_for_all_columns635,21872
+    fn records_mode_resolve_display_returns_values() {records_mode_resolve_display_returns_values658,22855
+    fn synthetic_record_info_returns_none_for_pivot_keys() {synthetic_record_info_returns_none_for_pivot_keys685,23940
+    fn synthetic_record_info_extracts_index_and_dim() {synthetic_record_info_extracts_index_and_dim694,24239
+    fn records_mode_includes_measure_in_dim_columns() {records_mode_includes_measure_in_dim_columns708,24795
+    fn records_mode_initial_layout_is_sorted() {records_mode_initial_layout_is_sorted736,25854
+    fn records_mode_new_record_appends_at_bottom() {records_mode_new_record_appends_at_bottom775,27437
+    fn coord(pairs: &[(&str, &str)]) -> CellKey {coord806,28817
+    fn two_cat_model() -> Model {two_cat_model815,29038
+    fn row_and_col_counts_match_item_counts() {row_and_col_counts_match_item_counts829,29442
+    fn cell_key_encodes_correct_coordinates() {cell_key_encodes_correct_coordinates837,29717
+    fn cell_key_out_of_bounds_returns_none() {cell_key_out_of_bounds_returns_none846,30034
+    fn cell_key_includes_page_coords() {cell_key_includes_page_coords854,30294
+    fn cell_key_round_trips_through_model_evaluate() {cell_key_round_trips_through_model_evaluate870,30981
+    fn labels_join_with_slash_for_multi_cat_axis() {labels_join_with_slash_for_multi_cat_axis883,31441
+    fn row_count_excludes_group_headers() {row_count_excludes_group_headers898,32046
+    fn group_header_emitted_at_group_boundary() {group_header_emitted_at_group_boundary917,32727
+    fn collapsed_group_has_header_but_no_data_items() {collapsed_group_has_header_but_no_data_items944,33695
+    fn ungrouped_items_produce_no_headers() {ungrouped_items_produce_no_headers975,34903
+    fn cell_key_correct_with_grouped_items() {cell_key_correct_with_grouped_items993,35396
+    fn data_row_to_visual_skips_headers() {data_row_to_visual_skips_headers1015,36188
+    fn data_col_to_visual_skips_headers() {data_col_to_visual_skips_headers1034,37000
+    fn row_group_for_finds_enclosing_group() {row_group_for_finds_enclosing_group1053,37776
+    fn row_group_for_returns_none_for_ungrouped() {row_group_for_returns_none_for_ungrouped1076,38529
+    fn col_group_for_finds_enclosing_group() {col_group_for_finds_enclosing_group1087,38946
+    fn col_group_for_returns_none_for_ungrouped() {col_group_for_returns_none_for_ungrouped1110,39716
 
 src/view/axis.rs,243
 pub enum Axis {Axis5,142
@@ -1278,7 +3456,7 @@ fn run_headless_commands(cmds: &[String], file: &Option<PathBuf>) -> Result<()>
 fn run_headless_script(script_path: &PathBuf, file: &Option<PathBuf>) -> Result<()> {run_headless_script363,10632
 fn get_initial_model(file_path: &Option<PathBuf>) -> Result<Model> {get_initial_model371,11110
 
-src/command/cmd/core.rs,4594
+src/command/cmd/core.rs,4869
 pub struct CmdContext<'a> {CmdContext13,328
     pub model: &'a Model,model14,356
     pub layout: &'a GridLayout,layout15,382
@@ -1299,97 +3477,105 @@ pub struct CmdContext<'a> {CmdContext13,328
     pub view_panel_cursor: usize,view_panel_cursor31,868
     pub tile_cat_idx: usize,tile_cat_idx33,958
     pub buffers: &'a HashMap<String, String>,buffers35,1014
-    pub view_back_stack: &'a [String],view_back_stack37,1116
-    pub view_forward_stack: &'a [String],view_forward_stack38,1155
-    pub display_value: String,display_value40,1281
-    pub visible_rows: usize,visible_rows42,1384
-    pub visible_cols: usize,visible_cols43,1413
-    pub expanded_cats: &'a std::collections::HashSet<String>,expanded_cats45,1488
-    pub key_code: KeyCode,key_code47,1594
-impl<'a> CmdContext<'a> {CmdContext50,1624
-    pub fn cell_key(&self) -> Option<CellKey> {cell_key51,1650
-    pub fn row_count(&self) -> usize {row_count54,1767
-    pub fn col_count(&self) -> usize {col_count57,1844
-    pub fn none_cats(&self) -> &[String] {none_cats60,1921
-impl<'a> CmdContext<'a> {CmdContext65,2004
-    pub fn cat_tree_entry(&self) -> Option<crate::ui::cat_tree::CatTreeEntry> {cat_tree_entry67,2099
-    pub fn cat_at_cursor(&self) -> Option<String> {cat_at_cursor74,2430
-    pub fn cat_tree_len(&self) -> usize {cat_tree_len79,2607
-pub trait Cmd: Debug + Send + Sync {Cmd85,2793
-    fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>>;execute86,2830
-    fn name(&self) -> &'static str;name90,3046
-pub type ParseFn = fn(&[String]) -> Result<Box<dyn Cmd>, String>;ParseFn94,3158
-pub type InteractiveFn = fn(&[String], &CmdContext) -> Result<Box<dyn Cmd>, String>;InteractiveFn100,3497
-type BoxParseFn = Box<dyn Fn(&[String]) -> Result<Box<dyn Cmd>, String>>;BoxParseFn102,3583
-type BoxInteractiveFn = Box<dyn Fn(&[String], &CmdContext) -> Result<Box<dyn Cmd>, String>>;BoxInteractiveFn103,3657
-struct CmdEntry {CmdEntry106,3827
-    name: &'static str,name107,3845
-    parse: BoxParseFn,parse108,3869
-    interactive: BoxInteractiveFn,interactive109,3892
-pub struct CmdRegistry {CmdRegistry114,4027
-    entries: Vec<CmdEntry>,entries115,4052
-    aliases: Vec<(&'static str, &'static str)>,aliases116,4080
-impl CmdRegistry {CmdRegistry119,4131
-    pub fn new() -> Self {new120,4150
-    pub fn alias(&mut self, short: &'static str, canonical: &'static str) {alias128,4348
-    fn resolve<'a>(&'a self, name: &'a str) -> &'a str {resolve133,4534
-    pub fn register(&mut self, prototype: &dyn Cmd, parse: ParseFn, interactive: InteractiveFn) register144,4897
-    pub fn register_pure(&mut self, prototype: &dyn Cmd, parse: ParseFn) {register_pure155,5368
-    pub fn register_nullary(&mut self, f: fn() -> Box<dyn Cmd>) {register_nullary171,5949
-    pub fn parse(&self, name: &str, args: &[String]) -> Result<Box<dyn Cmd>, String> {parse181,6290
-    pub fn interactive(interactive194,6812
-    pub fn names(&self) -> impl Iterator<Item = &'static str> + '_ {names210,7220
-pub(super) struct NamedCmd(pub(super) &'static str);NamedCmd218,7484
-impl Cmd for NamedCmd {NamedCmd219,7537
-    fn name(&self) -> &'static str {name220,7561
-    fn execute(&self, _: &CmdContext) -> Vec<Box<dyn Effect>> {execute223,7619
-pub(super) fn require_args(word: &str, args: &[String], n: usize) -> Result<(), String> {require_args228,7707
-pub(super) fn parse_cell_key_from_args(args: &[String]) -> crate::model::cell::CellKey {parse_cell_key_from_args240,8019
-pub(super) fn read_buffer(ctx: &CmdContext, name: &str) -> String {read_buffer252,8437
-pub(super) fn parse_panel(s: &str) -> Result<Panel, String> {parse_panel260,8649
-pub(super) fn parse_axis(s: &str) -> Result<Axis, String> {parse_axis269,8910
-mod tests {tests280,9232
-    fn parse_axis_recognizes_all_variants() {parse_axis_recognizes_all_variants284,9275
-    fn parse_axis_rejects_unknown() {parse_axis_rejects_unknown294,9609
+    pub view_back_stack: &'a [crate::ui::app::ViewFrame],view_back_stack37,1116
+    pub view_forward_stack: &'a [crate::ui::app::ViewFrame],view_forward_stack38,1174
+    pub has_drill_state: bool,has_drill_state40,1299
+    pub display_value: String,display_value42,1414
+    pub visible_rows: usize,visible_rows44,1517
+    pub visible_cols: usize,visible_cols45,1546
+    pub expanded_cats: &'a std::collections::HashSet<String>,expanded_cats47,1621
+    pub key_code: KeyCode,key_code49,1727
+impl<'a> CmdContext<'a> {CmdContext52,1757
+    pub fn is_records_mode(&self) -> bool {is_records_mode54,1853
+    pub fn cell_key(&self) -> Option<CellKey> {cell_key58,1942
+    pub fn synthetic_record_at_cursor(&self) -> Option<(usize, String)> {synthetic_record_at_cursor63,2136
+    pub fn row_count(&self) -> usize {row_count69,2321
+    pub fn col_count(&self) -> usize {col_count72,2398
+    pub fn none_cats(&self) -> &[String] {none_cats75,2475
+impl<'a> CmdContext<'a> {CmdContext80,2558
+    pub fn cat_tree_entry(&self) -> Option<crate::ui::cat_tree::CatTreeEntry> {cat_tree_entry82,2653
+    pub fn cat_at_cursor(&self) -> Option<String> {cat_at_cursor89,2984
+    pub fn cat_tree_len(&self) -> usize {cat_tree_len94,3161
+pub trait Cmd: Debug + Send + Sync {Cmd100,3347
+    fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>>;execute101,3384
+    fn name(&self) -> &'static str;name105,3600
+pub type ParseFn = fn(&[String]) -> Result<Box<dyn Cmd>, String>;ParseFn109,3712
+pub type InteractiveFn = fn(&[String], &CmdContext) -> Result<Box<dyn Cmd>, String>;InteractiveFn115,4051
+type BoxParseFn = Box<dyn Fn(&[String]) -> Result<Box<dyn Cmd>, String>>;BoxParseFn117,4137
+type BoxInteractiveFn = Box<dyn Fn(&[String], &CmdContext) -> Result<Box<dyn Cmd>, String>>;BoxInteractiveFn118,4211
+struct CmdEntry {CmdEntry121,4381
+    name: &'static str,name122,4399
+    parse: BoxParseFn,parse123,4423
+    interactive: BoxInteractiveFn,interactive124,4446
+pub struct CmdRegistry {CmdRegistry129,4581
+    entries: Vec<CmdEntry>,entries130,4606
+    aliases: Vec<(&'static str, &'static str)>,aliases131,4634
+impl CmdRegistry {CmdRegistry134,4685
+    pub fn new() -> Self {new135,4704
+    pub fn alias(&mut self, short: &'static str, canonical: &'static str) {alias143,4902
+    fn resolve<'a>(&'a self, name: &'a str) -> &'a str {resolve148,5088
+    pub fn register(&mut self, prototype: &dyn Cmd, parse: ParseFn, interactive: InteractiveFn) register159,5451
+    pub fn register_pure(&mut self, prototype: &dyn Cmd, parse: ParseFn) {register_pure170,5922
+    pub fn register_nullary(&mut self, f: fn() -> Box<dyn Cmd>) {register_nullary186,6503
+    pub fn parse(&self, name: &str, args: &[String]) -> Result<Box<dyn Cmd>, String> {parse196,6844
+    pub fn interactive(interactive209,7366
+    pub fn names(&self) -> impl Iterator<Item = &'static str> + '_ {names225,7774
+pub(super) struct NamedCmd(pub(super) &'static str);NamedCmd233,8038
+impl Cmd for NamedCmd {NamedCmd234,8091
+    fn name(&self) -> &'static str {name235,8115
+    fn execute(&self, _: &CmdContext) -> Vec<Box<dyn Effect>> {execute238,8173
+pub(super) fn require_args(word: &str, args: &[String], n: usize) -> Result<(), String> {require_args243,8261
+pub(super) fn parse_cell_key_from_args(args: &[String]) -> crate::model::cell::CellKey {parse_cell_key_from_args255,8573
+pub(super) fn read_buffer(ctx: &CmdContext, name: &str) -> String {read_buffer267,8991
+pub(super) fn parse_panel(s: &str) -> Result<Panel, String> {parse_panel275,9203
+pub(super) fn parse_axis(s: &str) -> Result<Axis, String> {parse_axis284,9464
+mod tests {tests295,9786
+    fn parse_axis_recognizes_all_variants() {parse_axis_recognizes_all_variants299,9829
+    fn parse_axis_rejects_unknown() {parse_axis_rejects_unknown309,10163
 
-src/command/cmd/commit.rs,2493
-mod tests {tests9,235
-    fn commit_formula_with_categories_adds_formula() {commit_formula_with_categories_adds_formula17,389
-    fn commit_formula_without_regular_categories_targets_measure() {commit_formula_without_regular_categories_targets_measure40,1257
-    fn commit_category_add_with_name_produces_add_effect() {commit_category_add_with_name_produces_add_effect61,1996
-    fn commit_category_add_with_empty_buffer_returns_to_panel() {commit_category_add_with_empty_buffer_returns_to_panel78,2582
-    fn commit_item_add_with_name_produces_add_item() {commit_item_add_with_name_produces_add_item95,3181
-    fn commit_item_add_outside_item_add_mode_returns_empty() {commit_item_add_outside_item_add_mode_returns_empty111,3813
-    fn commit_export_produces_export_and_normal_mode() {commit_export_produces_export_and_normal_mode121,4136
-fn commit_cell_value(key: &CellKey, value: &str, effects: &mut Vec<Box<dyn Effect>>) {commit_cell_value141,5058
-pub enum AdvanceDir {AdvanceDir165,5992
-    Down,Down167,6084
-    Right,Right169,6143
-pub struct CommitAndAdvance {CommitAndAdvance175,6324
-    pub key: CellKey,key176,6354
-    pub value: String,value177,6376
-    pub advance: AdvanceDir,advance178,6399
-    pub cursor: CursorState,cursor179,6428
-impl Cmd for CommitAndAdvance {CommitAndAdvance181,6459
-    fn name(&self) -> &'static str {name182,6491
-    fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute188,6686
-pub struct CommitFormula;CommitFormula218,7766
-impl Cmd for CommitFormula {CommitFormula219,7792
-    fn name(&self) -> &'static str {name220,7821
-    fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute223,7889
-fn commit_add_from_buffer(commit_add_from_buffer242,8782
-pub struct CommitCategoryAdd;CommitCategoryAdd265,9489
-impl Cmd for CommitCategoryAdd {CommitCategoryAdd266,9519
-    fn name(&self) -> &'static str {name267,9552
-    fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute270,9625
-pub struct CommitItemAdd;CommitItemAdd282,10002
-impl Cmd for CommitItemAdd {CommitItemAdd283,10028
-    fn name(&self) -> &'static str {name284,10057
-    fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute287,10126
-pub struct CommitExport;CommitExport309,10747
-impl Cmd for CommitExport {CommitExport310,10772
-    fn name(&self) -> &'static str {name311,10800
-    fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute314,10867
+src/command/cmd/commit.rs,2939
+mod tests {tests10,266
+    fn commit_formula_with_categories_adds_formula() {commit_formula_with_categories_adds_formula18,420
+    fn commit_formula_without_regular_categories_targets_measure() {commit_formula_without_regular_categories_targets_measure41,1288
+    fn commit_category_add_with_name_produces_add_effect() {commit_category_add_with_name_produces_add_effect62,2027
+    fn commit_category_add_with_empty_buffer_returns_to_panel() {commit_category_add_with_empty_buffer_returns_to_panel79,2613
+    fn commit_item_add_with_name_produces_add_item() {commit_item_add_with_name_produces_add_item96,3212
+    fn commit_item_add_outside_item_add_mode_returns_empty() {commit_item_add_outside_item_add_mode_returns_empty112,3844
+    fn commit_and_advance_threads_edit_mode_to_enter_edit_at_cursor() {commit_and_advance_threads_edit_mode_to_enter_edit_at_cursor125,4390
+    fn commit_export_produces_export_and_normal_mode() {commit_export_produces_export_and_normal_mode151,5415
+fn commit_regular_cell_value(key: &CellKey, value: &str, effects: &mut Vec<Box<dyn Effect>>) {commit_regular_cell_value172,6378
+fn stage_drill_edit(record_idx: usize, col_name: String, value: &str) -> Box<dyn Effect> {stage_drill_edit187,6974
+fn commit_plain_records_edit(commit_plain_records_edit196,7273
+fn commit_cell_value(commit_cell_value237,8302
+pub enum AdvanceDir {AdvanceDir257,8888
+    Down,Down259,8980
+    Right,Right261,9039
+pub struct CommitAndAdvance {CommitAndAdvance272,9482
+    pub key: CellKey,key273,9512
+    pub value: String,value274,9534
+    pub advance: AdvanceDir,advance275,9557
+    pub cursor: CursorState,cursor276,9586
+    pub edit_mode: AppMode,edit_mode277,9615
+impl Cmd for CommitAndAdvance {CommitAndAdvance279,9645
+    fn name(&self) -> &'static str {name280,9677
+    fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute286,9872
+pub struct CommitFormula;CommitFormula334,11770
+impl Cmd for CommitFormula {CommitFormula335,11796
+    fn name(&self) -> &'static str {name336,11825
+    fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute339,11893
+fn commit_add_from_buffer(commit_add_from_buffer359,12710
+pub struct CommitCategoryAdd;CommitCategoryAdd382,13417
+impl Cmd for CommitCategoryAdd {CommitCategoryAdd383,13447
+    fn name(&self) -> &'static str {name384,13480
+    fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute387,13553
+pub struct CommitItemAdd;CommitItemAdd399,13930
+impl Cmd for CommitItemAdd {CommitItemAdd400,13956
+    fn name(&self) -> &'static str {name401,13985
+    fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute404,14054
+pub struct CommitExport;CommitExport426,14675
+impl Cmd for CommitExport {CommitExport427,14700
+    fn name(&self) -> &'static str {name428,14728
+    fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute431,14795
 
 src/command/cmd/cell.rs,1886
 mod tests {tests6,90
@@ -1423,9 +3609,10 @@ impl Cmd for SaveCmd {SaveCmd191,5938
     fn name(&self) -> &'static str {name192,5961
     fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute195,6019
 
-src/command/cmd/registry.rs,113
-pub fn default_registry() -> CmdRegistry {default_registry19,459
-    macro_rules! reg_jump {reg_jump105,4345
+src/command/cmd/registry.rs,193
+fn parse_mode_name(s: &str) -> Result<AppMode, String> {parse_mode_name7,208
+pub fn default_registry() -> CmdRegistry {default_registry40,1375
+    macro_rules! reg_jump {reg_jump126,5261
 
 src/command/cmd/effect_cmds.rs,518
 mod tests {tests8,162
@@ -1455,46 +3642,46 @@ impl Cmd for TileAxisOp {TileAxisOp120,3709
     fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute128,3904
 
 src/command/cmd/grid.rs,2719
-mod tests {tests8,153
-    fn toggle_group_under_cursor_returns_empty_without_groups() {toggle_group_under_cursor_returns_empty_without_groups13,242
-    fn law_toggle_group_involution() {law_toggle_group_involution24,614
-    fn view_forward_with_empty_stack_shows_status() {view_forward_with_empty_stack_shows_status36,1071
-    fn view_back_with_empty_stack_shows_status() {view_back_with_empty_stack_shows_status51,1554
-    fn view_forward_with_stack_produces_effect() {view_forward_with_stack_produces_effect66,2036
-    fn view_back_with_stack_produces_apply_and_back() {view_back_with_stack_produces_apply_and_back83,2610
-    fn toggle_prune_empty_produces_toggle_and_dirty() {toggle_prune_empty_produces_toggle_and_dirty102,3318
-    fn drill_into_formula_cell_returns_data_records() {drill_into_formula_cell_returns_data_records120,4053
-pub struct ToggleGroupAtCursor {ToggleGroupAtCursor171,5975
-    pub is_row: bool,is_row172,6008
-impl Cmd for ToggleGroupAtCursor {ToggleGroupAtCursor174,6032
-    fn name(&self) -> &'static str {name175,6067
-    fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute182,6246
-pub struct HideSelectedRowItem;HideSelectedRowItem203,6803
-impl Cmd for HideSelectedRowItem {HideSelectedRowItem204,6835
-    fn name(&self) -> &'static str {name205,6870
-    fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute208,6946
-pub struct ViewNavigate {ViewNavigate241,7823
-    pub forward: bool,forward242,7849
-impl Cmd for ViewNavigate {ViewNavigate244,7874
-    fn name(&self) -> &'static str {name245,7902
-    fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute252,8049
-pub struct DrillIntoCell {DrillIntoCell276,8878
-    pub key: crate::model::cell::CellKey,key277,8905
-impl Cmd for DrillIntoCell {DrillIntoCell279,8949
-    fn name(&self) -> &'static str {name280,8978
-    fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute283,9047
-pub struct TogglePruneEmpty;TogglePruneEmpty373,12481
-impl Cmd for TogglePruneEmpty {TogglePruneEmpty374,12510
-    fn name(&self) -> &'static str {name375,12542
-    fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute378,12614
-pub struct ToggleRecordsMode;ToggleRecordsMode395,13218
-impl Cmd for ToggleRecordsMode {ToggleRecordsMode396,13248
-    fn name(&self) -> &'static str {name397,13281
-    fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute400,13354
-pub struct AddRecordRow;AddRecordRow440,14897
-impl Cmd for AddRecordRow {AddRecordRow441,14922
-    fn name(&self) -> &'static str {name442,14950
-    fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute445,15018
+mod tests {tests9,182
+    fn toggle_group_under_cursor_returns_empty_without_groups() {toggle_group_under_cursor_returns_empty_without_groups14,271
+    fn law_toggle_group_involution() {law_toggle_group_involution25,643
+    fn view_forward_with_empty_stack_shows_status() {view_forward_with_empty_stack_shows_status37,1100
+    fn view_back_with_empty_stack_shows_status() {view_back_with_empty_stack_shows_status52,1583
+    fn view_forward_with_stack_produces_effect() {view_forward_with_stack_produces_effect67,2065
+    fn view_back_with_stack_produces_apply_and_back() {view_back_with_stack_produces_apply_and_back87,2752
+    fn toggle_prune_empty_produces_toggle_and_dirty() {toggle_prune_empty_produces_toggle_and_dirty109,3573
+    fn drill_into_formula_cell_returns_data_records() {drill_into_formula_cell_returns_data_records127,4308
+pub struct ToggleGroupAtCursor {ToggleGroupAtCursor178,6230
+    pub is_row: bool,is_row179,6263
+impl Cmd for ToggleGroupAtCursor {ToggleGroupAtCursor181,6287
+    fn name(&self) -> &'static str {name182,6322
+    fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute189,6501
+pub struct HideSelectedRowItem;HideSelectedRowItem210,7058
+impl Cmd for HideSelectedRowItem {HideSelectedRowItem211,7090
+    fn name(&self) -> &'static str {name212,7125
+    fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute215,7201
+pub struct ViewNavigate {ViewNavigate248,8078
+    pub forward: bool,forward249,8104
+impl Cmd for ViewNavigate {ViewNavigate251,8129
+    fn name(&self) -> &'static str {name252,8157
+    fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute259,8304
+pub struct DrillIntoCell {DrillIntoCell283,9133
+    pub key: crate::model::cell::CellKey,key284,9160
+impl Cmd for DrillIntoCell {DrillIntoCell286,9204
+    fn name(&self) -> &'static str {name287,9233
+    fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute290,9302
+pub struct TogglePruneEmpty;TogglePruneEmpty380,12736
+impl Cmd for TogglePruneEmpty {TogglePruneEmpty381,12765
+    fn name(&self) -> &'static str {name382,12797
+    fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute385,12869
+pub struct ToggleRecordsMode;ToggleRecordsMode402,13473
+impl Cmd for ToggleRecordsMode {ToggleRecordsMode403,13503
+    fn name(&self) -> &'static str {name404,13536
+    fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute407,13609
+pub struct AddRecordRow;AddRecordRow450,15270
+impl Cmd for AddRecordRow {AddRecordRow451,15295
+    fn name(&self) -> &'static str {name452,15323
+    fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute455,15391
 
 src/command/cmd/text_buffer.rs,1949
 mod tests {tests9,164
@@ -1547,12 +3734,12 @@ pub(super) mod test_helpers {test_helpers19,341
     pub static EMPTY_EXPANDED: std::sync::LazyLock<std::collections::HashSet<String>> =EMPTY_EXPANDED36,830
     pub fn make_layout(model: &Model) -> GridLayout {make_layout39,985
     pub fn make_ctx<'a>(make_ctx43,1098
-    pub fn two_cat_model() -> Model {two_cat_model86,2450
-    pub fn three_cat_model_with_page() -> Model {three_cat_model_with_page97,2864
-    pub fn effects_debug(effects: &[Box<dyn Effect>]) -> String {effects_debug114,3613
-    pub fn make_registry() -> CmdRegistry {make_registry118,3719
+    pub fn two_cat_model() -> Model {two_cat_model87,2486
+    pub fn three_cat_model_with_page() -> Model {three_cat_model_with_page98,2900
+    pub fn effects_debug(effects: &[Box<dyn Effect>]) -> String {effects_debug115,3649
+    pub fn make_registry() -> CmdRegistry {make_registry119,3755
 
-src/command/cmd/navigation.rs,2755
+src/command/cmd/navigation.rs,2756
 pub struct CursorState {CursorState12,526
     pub row: usize,row13,551
     pub col: usize,col14,571
@@ -1590,18 +3777,18 @@ pub struct PagePrev;PagePrev211,6589
 impl Cmd for PagePrev {PagePrev212,6610
     fn name(&self) -> &'static str {name213,6634
     fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute216,6697
-mod tests {tests247,7623
-    fn move_selection_down_produces_set_selected() {move_selection_down_produces_set_selected252,7712
-    fn move_selection_clamps_to_bounds() {move_selection_clamps_to_bounds267,8185
-    fn enter_advance_moves_down() {enter_advance_moves_down282,8652
-    fn law_move_to_start_idempotent() {law_move_to_start_idempotent300,9195
-    fn law_sequence_associativity() {law_sequence_associativity324,9998
-    fn law_move_to_end_reaches_last_col() {law_move_to_end_reaches_last_col372,11290
-    fn page_next_with_no_page_cats_returns_empty() {page_next_with_no_page_cats_returns_empty392,11983
-    fn page_prev_with_no_page_cats_returns_empty() {page_prev_with_no_page_cats_returns_empty402,12291
-    fn page_next_cycles_through_page_items() {page_next_cycles_through_page_items412,12599
-    fn page_prev_cycles_backward() {page_prev_cycles_backward427,13083
-pub(super) fn page_cat_data(ctx: &CmdContext) -> Vec<(String, Vec<String>, usize)> {page_cat_data443,13615
+pub(super) fn page_cat_data(ctx: &CmdContext) -> Vec<(String, Vec<String>, usize)> {page_cat_data247,7678
+mod tests {tests282,8775
+    fn move_selection_down_produces_set_selected() {move_selection_down_produces_set_selected287,8864
+    fn move_selection_clamps_to_bounds() {move_selection_clamps_to_bounds302,9337
+    fn enter_advance_moves_down() {enter_advance_moves_down317,9804
+    fn law_move_to_start_idempotent() {law_move_to_start_idempotent335,10347
+    fn law_sequence_associativity() {law_sequence_associativity359,11150
+    fn law_move_to_end_reaches_last_col() {law_move_to_end_reaches_last_col407,12442
+    fn page_next_with_no_page_cats_returns_empty() {page_next_with_no_page_cats_returns_empty427,13135
+    fn page_prev_with_no_page_cats_returns_empty() {page_prev_with_no_page_cats_returns_empty437,13443
+    fn page_next_cycles_through_page_items() {page_next_cycles_through_page_items447,13751
+    fn page_prev_cycles_backward() {page_prev_cycles_backward462,14235
 
 src/command/cmd/panel.rs,5404
 mod tests {tests7,126
@@ -1706,59 +3893,58 @@ impl Cmd for ExitSearchMode {ExitSearchMode195,6125
     fn name(&self) -> &'static str {name196,6155
     fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute199,6225
 
-src/command/cmd/mode.rs,3176
+src/command/cmd/mode.rs,3285
 mod tests {tests8,151
-    fn enter_edit_mode_produces_editing_mode() {enter_edit_mode_produces_editing_mode14,269
-    fn enter_tile_select_with_categories() {enter_tile_select_with_categories29,782
-    fn enter_tile_select_no_categories() {enter_tile_select_no_categories45,1279
-    fn enter_export_prompt_sets_mode() {enter_export_prompt_sets_mode56,1612
-    fn force_quit_always_produces_quit_mode() {force_quit_always_produces_quit_mode70,2046
-    fn save_and_quit_produces_save_then_quit() {save_and_quit_produces_save_then_quit83,2497
-    fn edit_or_drill_without_aggregation_enters_edit() {edit_or_drill_without_aggregation_enters_edit96,2984
-    fn enter_search_mode_sets_flag_and_clears_query() {enter_search_mode_sets_flag_and_clears_query107,3384
-pub struct EnterMode(pub AppMode);EnterMode129,4191
-impl Cmd for EnterMode {EnterMode130,4226
-    fn name(&self) -> &'static str {name131,4251
-    fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute134,4315
-pub struct ForceQuit;ForceQuit149,4833
-impl Cmd for ForceQuit {ForceQuit150,4855
-    fn name(&self) -> &'static str {name151,4880
-    fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute154,4944
-pub struct Quit;Quit161,5150
-impl Cmd for Quit {Quit162,5167
-    fn name(&self) -> &'static str {name163,5187
-    fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute166,5242
-pub struct SaveAndQuit;SaveAndQuit179,5588
-impl Cmd for SaveAndQuit {SaveAndQuit180,5612
-    fn name(&self) -> &'static str {name181,5639
-    fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute184,5695
-pub struct EnterEditMode {EnterEditMode193,6105
-    pub initial_value: String,initial_value194,6132
-impl Cmd for EnterEditMode {EnterEditMode196,6165
-    fn name(&self) -> &'static str {name197,6194
-    fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute200,6263
-pub struct EditOrDrill;EditOrDrill215,6805
-impl Cmd for EditOrDrill {EditOrDrill216,6829
-    fn name(&self) -> &'static str {name217,6856
-    fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute220,6923
-pub struct EnterEditAtCursorCmd;EnterEditAtCursorCmd253,8202
-impl Cmd for EnterEditAtCursorCmd {EnterEditAtCursorCmd254,8235
-    fn name(&self) -> &'static str {name255,8271
-    fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute258,8345
-pub struct EnterExportPrompt;EnterExportPrompt265,8518
-impl Cmd for EnterExportPrompt {EnterExportPrompt266,8548
-    fn name(&self) -> &'static str {name267,8581
-    fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute270,8654
-pub struct EnterSearchMode;EnterSearchMode277,8830
-impl Cmd for EnterSearchMode {EnterSearchMode278,8858
-    fn name(&self) -> &'static str {name279,8889
-    fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute282,8949
-pub struct EnterTileSelect;EnterTileSelect292,9206
-impl Cmd for EnterTileSelect {EnterTileSelect293,9234
-    fn name(&self) -> &'static str {name294,9265
-    fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute297,9336
+    fn enter_tile_select_with_categories() {enter_tile_select_with_categories14,269
+    fn enter_tile_select_no_categories() {enter_tile_select_no_categories30,766
+    fn enter_export_prompt_sets_mode() {enter_export_prompt_sets_mode41,1099
+    fn force_quit_always_produces_quit_mode() {force_quit_always_produces_quit_mode55,1533
+    fn save_and_quit_produces_save_then_quit() {save_and_quit_produces_save_then_quit68,1984
+    fn edit_or_drill_without_aggregation_enters_edit() {edit_or_drill_without_aggregation_enters_edit81,2471
+    fn edit_or_drill_passes_records_editing_mode_through() {edit_or_drill_passes_records_editing_mode_through101,3315
+    fn enter_edit_at_cursor_cmd_passes_target_mode_to_effect() {enter_edit_at_cursor_cmd_passes_target_mode_to_effect123,4188
+    fn edit_or_drill_pre_fills_edit_buffer_with_display_value() {edit_or_drill_pre_fills_edit_buffer_with_display_value143,4917
+    fn enter_search_mode_sets_flag_and_clears_query() {enter_search_mode_sets_flag_and_clears_query161,5545
+pub struct EnterMode(pub AppMode);EnterMode183,6352
+impl Cmd for EnterMode {EnterMode184,6387
+    fn name(&self) -> &'static str {name185,6412
+    fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute188,6476
+pub struct ForceQuit;ForceQuit203,6994
+impl Cmd for ForceQuit {ForceQuit204,7016
+    fn name(&self) -> &'static str {name205,7041
+    fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute208,7105
+pub struct Quit;Quit215,7311
+impl Cmd for Quit {Quit216,7328
+    fn name(&self) -> &'static str {name217,7348
+    fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute220,7403
+pub struct SaveAndQuit;SaveAndQuit233,7749
+impl Cmd for SaveAndQuit {SaveAndQuit234,7773
+    fn name(&self) -> &'static str {name235,7800
+    fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute238,7856
+pub struct EditOrDrill {EditOrDrill254,8698
+    pub edit_mode: AppMode,edit_mode255,8723
+impl Cmd for EditOrDrill {EditOrDrill257,8753
+    fn name(&self) -> &'static str {name258,8780
+    fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute261,8847
+pub struct EnterEditAtCursorCmd {EnterEditAtCursorCmd295,10290
+    pub target_mode: AppMode,target_mode296,10324
+impl Cmd for EnterEditAtCursorCmd {EnterEditAtCursorCmd298,10356
+    fn name(&self) -> &'static str {name299,10392
+    fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute302,10466
+pub struct EnterExportPrompt;EnterExportPrompt311,10702
+impl Cmd for EnterExportPrompt {EnterExportPrompt312,10732
+    fn name(&self) -> &'static str {name313,10765
+    fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute316,10838
+pub struct EnterSearchMode;EnterSearchMode323,11014
+impl Cmd for EnterSearchMode {EnterSearchMode324,11042
+    fn name(&self) -> &'static str {name325,11073
+    fn execute(&self, _ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute328,11133
+pub struct EnterTileSelect;EnterTileSelect338,11390
+impl Cmd for EnterTileSelect {EnterTileSelect339,11418
+    fn name(&self) -> &'static str {name340,11449
+    fn execute(&self, ctx: &CmdContext) -> Vec<Box<dyn Effect>> {execute343,11520
 
-src/command/keymap.rs,5000
+src/command/keymap.rs,5093
 fn format_key_label(code: &KeyCode) -> String {format_key_label13,301
 pub enum KeyPattern {KeyPattern38,1303
     Key(KeyCode, KeyModifiers),Key40,1359
@@ -1779,67 +3965,69 @@ pub enum ModeKey {ModeKey49,1621
     CommandMode,CommandMode61,1808
     SearchMode,SearchMode62,1825
     ImportWizard,ImportWizard63,1841
-impl ModeKey {ModeKey66,1862
-    pub fn from_app_mode(mode: &AppMode, search_mode: bool) -> Option<Self> {from_app_mode67,1877
-pub enum Binding {Binding90,2989
-    Cmd {Cmd92,3088
-    Prefix(Arc<Keymap>),Prefix97,3203
-    Sequence(Vec<(&'static str, Vec<String>)>),Sequence99,3307
-pub struct Keymap {Keymap106,3589
-    bindings: HashMap<KeyPattern, Binding>,bindings107,3609
-    parent: Option<Arc<Keymap>>,parent108,3653
-impl fmt::Debug for Keymap {Keymap111,3689
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {fmt112,3718
-impl Keymap {Keymap119,3903
-    pub fn new() -> Self {new120,3917
-    pub fn with_parent(parent: Arc<Keymap>) -> Self {with_parent129,4182
-    pub fn bind(&mut self, key: KeyCode, mods: KeyModifiers, name: &'static str) {bind137,4388
-    pub fn bind_args(bind_args145,4661
-    pub fn bind_prefix(&mut self, key: KeyCode, mods: KeyModifiers, sub: Arc<Keymap>) {bind_prefix157,4977
-    pub fn bind_seq(bind_seq163,5231
-    pub fn bind_any_char(&mut self, name: &'static str, args: Vec<String>) {bind_any_char174,5525
-    pub fn bind_any(&mut self, name: &'static str) {bind_any180,5747
-    pub fn binding_hints(&self) -> Vec<(String, &'static str)> {binding_hints187,6068
-        let mut hints: Vec<(String, &'static str)> = selfstr188,6133
-    fn lookup_local(&self, key: KeyCode, mods: KeyModifiers) -> Option<&Binding> {lookup_local215,7143
-    pub fn lookup(&self, key: KeyCode, mods: KeyModifiers) -> Option<&Binding> {lookup237,8016
-    pub fn dispatch(dispatch243,8304
-pub struct SetTransientKeymap(pub Arc<Keymap>);SetTransientKeymap271,9254
-impl Effect for SetTransientKeymap {SetTransientKeymap273,9303
-    fn apply(&self, app: &mut crate::ui::app::App) {apply274,9340
-pub struct KeymapSet {KeymapSet280,9521
-    mode_maps: HashMap<ModeKey, Arc<Keymap>>,mode_maps281,9544
-    registry: CmdRegistry,registry282,9590
-impl KeymapSet {KeymapSet285,9620
-    pub fn new(registry: CmdRegistry) -> Self {new286,9637
-    pub fn registry(&self) -> &CmdRegistry {registry293,9778
-    pub fn insert(&mut self, mode: ModeKey, keymap: Arc<Keymap>) {insert297,9853
-    pub fn dispatch(dispatch302,10040
-    pub fn dispatch_transient(dispatch_transient314,10458
-    pub fn default_keymaps() -> Self {default_keymaps325,10764
-mod tests {tests908,32478
-    fn lookup_exact_match() {lookup_exact_match914,32669
-    fn lookup_exact_with_ctrl() {lookup_exact_with_ctrl922,32959
-    fn lookup_char_falls_back_to_none_mods() {lookup_char_falls_back_to_none_mods930,33257
-    fn lookup_non_char_does_not_fall_back_to_none_mods() {lookup_non_char_does_not_fall_back_to_none_mods940,33715
-    fn lookup_char_falls_to_any_char() {lookup_char_falls_to_any_char950,34128
-    fn lookup_non_char_skips_any_char() {lookup_non_char_skips_any_char964,34496
-    fn lookup_any_matches_everything() {lookup_any_matches_everything973,34804
-    fn lookup_exact_takes_priority_over_any_char() {lookup_exact_takes_priority_over_any_char987,35149
-    fn lookup_any_char_takes_priority_over_any() {lookup_any_char_takes_priority_over_any1002,35591
-    fn lookup_non_char_falls_to_any_not_any_char() {lookup_non_char_falls_to_any_not_any_char1017,36003
-    fn lookup_ctrl_char_with_only_none_binding_falls_through() {lookup_ctrl_char_with_only_none_binding_falls_through1032,36412
-    fn mode_key_normal_no_search() {mode_key_normal_no_search1043,36918
-    fn mode_key_normal_with_search_overrides() {mode_key_normal_with_search_overrides1049,37087
-    fn mode_key_help() {mode_key_help1055,37271
-    fn mode_key_quit_returns_none() {mode_key_quit_returns_none1061,37424
-    fn lookup_returns_prefix_binding() {lookup_returns_prefix_binding1069,37751
-    fn lookup_returns_sequence_binding() {lookup_returns_sequence_binding1078,38082
-    fn default_keymaps_has_all_modes() {default_keymaps_has_all_modes1092,38633
-    fn normal_mode_has_basic_movement() {normal_mode_has_basic_movement1120,39445
-    fn editing_mode_has_any_char_and_esc() {editing_mode_has_any_char_and_esc1144,40229
-    fn search_mode_has_any_char_and_esc() {search_mode_has_any_char_and_esc1158,40705
-    fn import_wizard_has_any_catchall() {import_wizard_has_any_catchall1170,41099
+    RecordsNormal,RecordsNormal64,1859
+    RecordsEditing,RecordsEditing65,1878
+impl ModeKey {ModeKey68,1901
+    pub fn from_app_mode(mode: &AppMode, search_mode: bool) -> Option<Self> {from_app_mode69,1916
+pub enum Binding {Binding94,3173
+    Cmd {Cmd96,3272
+    Prefix(Arc<Keymap>),Prefix101,3387
+    Sequence(Vec<(&'static str, Vec<String>)>),Sequence103,3491
+pub struct Keymap {Keymap110,3773
+    bindings: HashMap<KeyPattern, Binding>,bindings111,3793
+    parent: Option<Arc<Keymap>>,parent112,3837
+impl fmt::Debug for Keymap {Keymap115,3873
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {fmt116,3902
+impl Keymap {Keymap123,4087
+    pub fn new() -> Self {new124,4101
+    pub fn with_parent(parent: Arc<Keymap>) -> Self {with_parent133,4366
+    pub fn bind(&mut self, key: KeyCode, mods: KeyModifiers, name: &'static str) {bind141,4572
+    pub fn bind_args(bind_args149,4845
+    pub fn bind_prefix(&mut self, key: KeyCode, mods: KeyModifiers, sub: Arc<Keymap>) {bind_prefix161,5161
+    pub fn bind_seq(bind_seq167,5415
+    pub fn bind_any_char(&mut self, name: &'static str, args: Vec<String>) {bind_any_char178,5709
+    pub fn bind_any(&mut self, name: &'static str) {bind_any184,5931
+    pub fn binding_hints(&self) -> Vec<(String, &'static str)> {binding_hints191,6252
+        let mut hints: Vec<(String, &'static str)> = selfstr192,6317
+    fn lookup_local(&self, key: KeyCode, mods: KeyModifiers) -> Option<&Binding> {lookup_local219,7327
+    pub fn lookup(&self, key: KeyCode, mods: KeyModifiers) -> Option<&Binding> {lookup241,8200
+    pub fn dispatch(dispatch247,8488
+pub struct SetTransientKeymap(pub Arc<Keymap>);SetTransientKeymap275,9438
+impl Effect for SetTransientKeymap {SetTransientKeymap277,9487
+    fn apply(&self, app: &mut crate::ui::app::App) {apply278,9524
+pub struct KeymapSet {KeymapSet284,9705
+    mode_maps: HashMap<ModeKey, Arc<Keymap>>,mode_maps285,9728
+    registry: CmdRegistry,registry286,9774
+impl KeymapSet {KeymapSet289,9804
+    pub fn new(registry: CmdRegistry) -> Self {new290,9821
+    pub fn registry(&self) -> &CmdRegistry {registry297,9962
+    pub fn insert(&mut self, mode: ModeKey, keymap: Arc<Keymap>) {insert301,10037
+    pub fn dispatch(dispatch306,10224
+    pub fn dispatch_transient(dispatch_transient318,10642
+    pub fn default_keymaps() -> Self {default_keymaps329,10948
+mod tests {tests978,34976
+    fn lookup_exact_match() {lookup_exact_match984,35167
+    fn lookup_exact_with_ctrl() {lookup_exact_with_ctrl992,35457
+    fn lookup_char_falls_back_to_none_mods() {lookup_char_falls_back_to_none_mods1000,35755
+    fn lookup_non_char_does_not_fall_back_to_none_mods() {lookup_non_char_does_not_fall_back_to_none_mods1010,36213
+    fn lookup_char_falls_to_any_char() {lookup_char_falls_to_any_char1020,36626
+    fn lookup_non_char_skips_any_char() {lookup_non_char_skips_any_char1034,36994
+    fn lookup_any_matches_everything() {lookup_any_matches_everything1043,37302
+    fn lookup_exact_takes_priority_over_any_char() {lookup_exact_takes_priority_over_any_char1057,37647
+    fn lookup_any_char_takes_priority_over_any() {lookup_any_char_takes_priority_over_any1072,38089
+    fn lookup_non_char_falls_to_any_not_any_char() {lookup_non_char_falls_to_any_not_any_char1087,38501
+    fn lookup_ctrl_char_with_only_none_binding_falls_through() {lookup_ctrl_char_with_only_none_binding_falls_through1102,38910
+    fn mode_key_normal_no_search() {mode_key_normal_no_search1113,39416
+    fn mode_key_normal_with_search_overrides() {mode_key_normal_with_search_overrides1119,39585
+    fn mode_key_help() {mode_key_help1125,39769
+    fn mode_key_quit_returns_none() {mode_key_quit_returns_none1131,39922
+    fn lookup_returns_prefix_binding() {lookup_returns_prefix_binding1139,40249
+    fn lookup_returns_sequence_binding() {lookup_returns_sequence_binding1148,40580
+    fn default_keymaps_has_all_modes() {default_keymaps_has_all_modes1162,41131
+    fn normal_mode_has_basic_movement() {normal_mode_has_basic_movement1192,42016
+    fn editing_mode_has_any_char_and_esc() {editing_mode_has_any_char_and_esc1216,42800
+    fn search_mode_has_any_char_and_esc() {search_mode_has_any_char_and_esc1230,43276
+    fn import_wizard_has_any_catchall() {import_wizard_has_any_catchall1242,43670
 
 src/command/mod.rs,80
 pub mod cmd;cmd8,323
@@ -1950,81 +4138,81 @@ impl ImportPipeline {ImportPipeline28,984
     pub fn needs_path_selection(&self) -> bool {needs_path_selection60,1994
     pub fn preview_summary(&self) -> String {preview_summary64,2137
     pub fn build_model(&self) -> Result<Model> {build_model84,2916
-pub enum WizardStep {WizardStep245,9217
-    Preview,Preview246,9239
-    SelectArrayPath,SelectArrayPath247,9252
-    ReviewProposals,ReviewProposals248,9273
-    ConfigureDates,ConfigureDates249,9294
-    DefineFormulas,DefineFormulas250,9314
-    NameModel,NameModel251,9334
-    Done,Done252,9349
-pub struct ImportWizard {ImportWizard259,9615
-    pub pipeline: ImportPipeline,pipeline260,9641
-    pub step: WizardStep,step261,9675
-    pub cursor: usize,cursor263,9768
-    pub message: Option<String>,message265,9862
-    pub formula_editing: bool,formula_editing267,9945
-    pub formula_buffer: String,formula_buffer269,10020
-impl ImportWizard {ImportWizard272,10055
-    pub fn new(raw: Value) -> Self {new273,10075
-    pub fn advance(&mut self) {advance296,10803
-    fn has_time_categories(&self) -> bool {has_time_categories322,11788
-    pub fn time_category_proposals(&self) -> Vec<&FieldProposal> {time_category_proposals330,12072
-    pub fn confirm_path(&mut self) {confirm_path338,12334
-    pub fn move_cursor(&mut self, delta: i32) {move_cursor348,12789
-    pub fn toggle_proposal(&mut self) {toggle_proposal368,13562
-    pub fn cycle_proposal_kind(&mut self) {cycle_proposal_kind375,13800
-    pub fn push_name_char(&mut self, c: char) {push_name_char389,14467
-    pub fn pop_name_char(&mut self) {pop_name_char392,14563
-    fn date_config_item_count(&self) -> usize {date_config_item_count400,14982
-    pub fn date_config_at_cursor(&self) -> Option<(usize, DateComponent)> {date_config_at_cursor405,15160
-    fn time_category_indices(&self) -> Vec<usize> {time_category_indices421,15754
-    pub fn toggle_date_component(&mut self) {toggle_date_component434,16161
-    pub fn push_formula_char(&mut self, c: char) {push_formula_char452,16928
-    pub fn pop_formula_char(&mut self) {pop_formula_char460,17150
-    pub fn confirm_formula(&mut self) {confirm_formula465,17307
-    pub fn delete_formula(&mut self) {delete_formula476,17704
-    pub fn start_formula_edit(&mut self) {start_formula_edit486,18037
-    pub fn cancel_formula_edit(&mut self) {cancel_formula_edit492,18193
-    pub fn sample_formulas(&self) -> Vec<String> {sample_formulas498,18380
-    pub fn build_model(&self) -> Result<Model> {build_model522,19259
-mod tests {tests528,19366
-    fn flat_array_auto_selected() {flat_array_auto_selected534,19492
-    fn numeric_field_proposed_as_measure() {numeric_field_proposed_as_measure546,19895
-    fn low_cardinality_string_field_proposed_as_category() {low_cardinality_string_field_proposed_as_category554,20216
-    fn nested_json_needs_path_selection_when_multiple_arrays() {nested_json_needs_path_selection_when_multiple_arrays566,20660
-    fn single_nested_array_auto_selected() {single_nested_array_auto_selected576,20971
-    fn select_path_populates_records_and_proposals() {select_path_populates_records_and_proposals589,21343
-    fn build_model_fails_with_no_accepted_categories() {build_model_fails_with_no_accepted_categories601,21697
-    fn build_model_creates_categories_and_measure_category() {build_model_creates_categories_and_measure_category611,22007
-    fn label_fields_imported_as_label_category_coords() {label_fields_imported_as_label_category_coords623,22423
-    fn label_category_defaults_to_none_axis() {label_category_defaults_to_none_axis650,23728
-    fn build_model_cells_match_source_data() {build_model_cells_match_source_data663,24216
-    fn model_name_defaults_to_imported_model() {model_name_defaults_to_imported_model690,25121
-    fn build_model_adds_formulas_from_pipeline() {build_model_adds_formulas_from_pipeline697,25322
-    fn build_model_extracts_date_month_component() {build_model_extracts_date_month_component716,26090
-    fn sample_wizard() -> ImportWizard {sample_wizard745,27317
-    fn wizard_starts_at_review_proposals_for_flat_array() {wizard_starts_at_review_proposals_for_flat_array754,27616
-    fn wizard_starts_at_select_array_path_for_multi_path_object() {wizard_starts_at_select_array_path_for_multi_path_object760,27785
-    fn wizard_advance_from_review_proposals_skips_dates_when_none() {wizard_advance_from_review_proposals_skips_dates_when_none770,28085
-    fn wizard_advance_full_sequence() {wizard_advance_full_sequence779,28418
-    fn wizard_move_cursor_clamps() {wizard_move_cursor_clamps794,28893
-    fn wizard_toggle_proposal() {wizard_toggle_proposal809,29326
-    fn wizard_cycle_proposal_kind() {wizard_cycle_proposal_kind819,29671
-    fn wizard_model_name_editing() {wizard_model_name_editing832,30132
-    fn wizard_confirm_path() {wizard_confirm_path843,30450
-    fn wizard_formula_lifecycle() {wizard_formula_lifecycle859,31111
-    fn wizard_delete_formula() {wizard_delete_formula897,32271
-    fn wizard_delete_formula_at_zero() {wizard_delete_formula_at_zero909,32685
-    fn wizard_confirm_empty_formula_is_noop() {wizard_confirm_empty_formula_is_noop919,32973
-    fn sample_formulas_with_two_measures() {sample_formulas_with_two_measures929,33382
-    fn sample_formulas_with_one_measure() {sample_formulas_with_one_measure939,33740
-    fn sample_formulas_with_no_measures() {sample_formulas_with_no_measures951,34148
-    fn preview_summary_for_array() {preview_summary_for_array963,34601
-    fn preview_summary_for_object() {preview_summary_for_object974,34905
-    fn wizard_date_config_toggle() {wizard_date_config_toggle988,35410
-    fn wizard_date_config_at_cursor_mapping() {wizard_date_config_at_cursor_mapping1024,36707
-    fn build_model_record_with_missing_category_value_skipped() {build_model_record_with_missing_category_value_skipped1048,37695
-    fn build_model_with_integer_category_values() {build_model_with_integer_category_values1066,38390
-    fn build_model_formulas_without_measure_category() {build_model_formulas_without_measure_category1084,39191
-    fn build_model_date_components_appear_in_cell_keys() {build_model_date_components_appear_in_cell_keys1102,40030
+pub enum WizardStep {WizardStep247,9217
+    Preview,Preview248,9239
+    SelectArrayPath,SelectArrayPath249,9252
+    ReviewProposals,ReviewProposals250,9273
+    ConfigureDates,ConfigureDates251,9294
+    DefineFormulas,DefineFormulas252,9314
+    NameModel,NameModel253,9334
+    Done,Done254,9349
+pub struct ImportWizard {ImportWizard261,9615
+    pub pipeline: ImportPipeline,pipeline262,9641
+    pub step: WizardStep,step263,9675
+    pub cursor: usize,cursor265,9768
+    pub message: Option<String>,message267,9862
+    pub formula_editing: bool,formula_editing269,9945
+    pub formula_buffer: String,formula_buffer271,10020
+impl ImportWizard {ImportWizard274,10055
+    pub fn new(raw: Value) -> Self {new275,10075
+    pub fn advance(&mut self) {advance298,10803
+    fn has_time_categories(&self) -> bool {has_time_categories324,11788
+    pub fn time_category_proposals(&self) -> Vec<&FieldProposal> {time_category_proposals332,12072
+    pub fn confirm_path(&mut self) {confirm_path340,12334
+    pub fn move_cursor(&mut self, delta: i32) {move_cursor350,12789
+    pub fn toggle_proposal(&mut self) {toggle_proposal370,13562
+    pub fn cycle_proposal_kind(&mut self) {cycle_proposal_kind377,13800
+    pub fn push_name_char(&mut self, c: char) {push_name_char391,14467
+    pub fn pop_name_char(&mut self) {pop_name_char394,14563
+    fn date_config_item_count(&self) -> usize {date_config_item_count402,14982
+    pub fn date_config_at_cursor(&self) -> Option<(usize, DateComponent)> {date_config_at_cursor407,15160
+    fn time_category_indices(&self) -> Vec<usize> {time_category_indices423,15754
+    pub fn toggle_date_component(&mut self) {toggle_date_component436,16161
+    pub fn push_formula_char(&mut self, c: char) {push_formula_char454,16928
+    pub fn pop_formula_char(&mut self) {pop_formula_char462,17150
+    pub fn confirm_formula(&mut self) {confirm_formula467,17307
+    pub fn delete_formula(&mut self) {delete_formula478,17704
+    pub fn start_formula_edit(&mut self) {start_formula_edit488,18037
+    pub fn cancel_formula_edit(&mut self) {cancel_formula_edit494,18193
+    pub fn sample_formulas(&self) -> Vec<String> {sample_formulas500,18380
+    pub fn build_model(&self) -> Result<Model> {build_model524,19259
+mod tests {tests530,19366
+    fn flat_array_auto_selected() {flat_array_auto_selected536,19492
+    fn numeric_field_proposed_as_measure() {numeric_field_proposed_as_measure548,19895
+    fn low_cardinality_string_field_proposed_as_category() {low_cardinality_string_field_proposed_as_category556,20216
+    fn nested_json_needs_path_selection_when_multiple_arrays() {nested_json_needs_path_selection_when_multiple_arrays568,20660
+    fn single_nested_array_auto_selected() {single_nested_array_auto_selected578,20971
+    fn select_path_populates_records_and_proposals() {select_path_populates_records_and_proposals591,21343
+    fn build_model_fails_with_no_accepted_categories() {build_model_fails_with_no_accepted_categories603,21697
+    fn build_model_creates_categories_and_measure_category() {build_model_creates_categories_and_measure_category613,22007
+    fn label_fields_imported_as_label_category_coords() {label_fields_imported_as_label_category_coords625,22423
+    fn label_category_defaults_to_none_axis() {label_category_defaults_to_none_axis652,23728
+    fn build_model_cells_match_source_data() {build_model_cells_match_source_data665,24216
+    fn model_name_defaults_to_imported_model() {model_name_defaults_to_imported_model692,25121
+    fn build_model_adds_formulas_from_pipeline() {build_model_adds_formulas_from_pipeline699,25322
+    fn build_model_extracts_date_month_component() {build_model_extracts_date_month_component718,26090
+    fn sample_wizard() -> ImportWizard {sample_wizard747,27317
+    fn wizard_starts_at_review_proposals_for_flat_array() {wizard_starts_at_review_proposals_for_flat_array756,27616
+    fn wizard_starts_at_select_array_path_for_multi_path_object() {wizard_starts_at_select_array_path_for_multi_path_object762,27785
+    fn wizard_advance_from_review_proposals_skips_dates_when_none() {wizard_advance_from_review_proposals_skips_dates_when_none772,28085
+    fn wizard_advance_full_sequence() {wizard_advance_full_sequence781,28418
+    fn wizard_move_cursor_clamps() {wizard_move_cursor_clamps796,28893
+    fn wizard_toggle_proposal() {wizard_toggle_proposal811,29326
+    fn wizard_cycle_proposal_kind() {wizard_cycle_proposal_kind821,29671
+    fn wizard_model_name_editing() {wizard_model_name_editing834,30132
+    fn wizard_confirm_path() {wizard_confirm_path845,30450
+    fn wizard_formula_lifecycle() {wizard_formula_lifecycle861,31111
+    fn wizard_delete_formula() {wizard_delete_formula899,32271
+    fn wizard_delete_formula_at_zero() {wizard_delete_formula_at_zero911,32685
+    fn wizard_confirm_empty_formula_is_noop() {wizard_confirm_empty_formula_is_noop921,32973
+    fn sample_formulas_with_two_measures() {sample_formulas_with_two_measures931,33382
+    fn sample_formulas_with_one_measure() {sample_formulas_with_one_measure941,33740
+    fn sample_formulas_with_no_measures() {sample_formulas_with_no_measures953,34148
+    fn preview_summary_for_array() {preview_summary_for_array965,34601
+    fn preview_summary_for_object() {preview_summary_for_object976,34905
+    fn wizard_date_config_toggle() {wizard_date_config_toggle990,35410
+    fn wizard_date_config_at_cursor_mapping() {wizard_date_config_at_cursor_mapping1018,36495
+    fn build_model_record_with_missing_category_value_skipped() {build_model_record_with_missing_category_value_skipped1042,37483
+    fn build_model_with_integer_category_values() {build_model_with_integer_category_values1060,38178
+    fn build_model_formulas_without_measure_category() {build_model_formulas_without_measure_category1078,38979
+    fn build_model_date_components_appear_in_cell_keys() {build_model_date_components_appear_in_cell_keys1096,39818
