diff --git a/src/command/cmd/core.rs b/src/command/cmd/core.rs index 049d7dc..fa868c0 100644 --- a/src/command/cmd/core.rs +++ b/src/command/cmd/core.rs @@ -48,9 +48,22 @@ pub struct CmdContext<'a> { } impl<'a> CmdContext<'a> { + /// Return true when the current layout is a records-mode layout. + pub fn is_records_mode(&self) -> bool { + self.layout.is_records_mode() + } + pub fn cell_key(&self) -> Option { self.layout.cell_key(self.selected.0, self.selected.1) } + + /// Return synthetic record coordinates for the current cursor, if any. + pub fn synthetic_record_at_cursor(&self) -> Option<(usize, String)> { + self.cell_key() + .as_ref() + .and_then(crate::view::synthetic_record_info) + } + pub fn row_count(&self) -> usize { self.layout.row_count() } diff --git a/src/command/cmd/grid.rs b/src/command/cmd/grid.rs index cce6573..a0176ab 100644 --- a/src/command/cmd/grid.rs +++ b/src/command/cmd/grid.rs @@ -443,12 +443,7 @@ impl Cmd for AddRecordRow { "add-record-row" } fn execute(&self, ctx: &CmdContext) -> Vec> { - let is_records = ctx - .cell_key() - .as_ref() - .and_then(crate::view::synthetic_record_info) - .is_some(); - if !is_records { + if !ctx.is_records_mode() { return vec![effect::set_status( "add-record-row only works in records mode", )]; diff --git a/src/command/cmd/mode.rs b/src/command/cmd/mode.rs index db269ad..0a705f3 100644 --- a/src/command/cmd/mode.rs +++ b/src/command/cmd/mode.rs @@ -228,11 +228,7 @@ impl Cmd for EditOrDrill { .unwrap_or(false) }); // In records mode (synthetic key), always edit directly — no drilling. - let is_synthetic = ctx - .cell_key() - .as_ref() - .and_then(crate::view::synthetic_record_info) - .is_some(); + let is_synthetic = ctx.synthetic_record_at_cursor().is_some(); let is_aggregated = !is_synthetic && regular_none; if is_aggregated { let Some(key) = ctx.cell_key().clone() else { diff --git a/src/ui/app.rs b/src/ui/app.rs index 137c800..1fec708 100644 --- a/src/ui/app.rs +++ b/src/ui/app.rs @@ -712,6 +712,35 @@ mod tests { ); } + /// Regression: pressing `o` in an empty records view should create the + /// first synthetic row instead of only entering edit mode on empty space. + #[test] + fn add_record_row_in_empty_records_view_creates_first_row() { + let mut m = Model::new("T"); + m.add_category("Region").unwrap(); + m.category_mut("Region").unwrap().add_item("East"); + let mut app = App::new(m, None); + + app.handle_key(KeyEvent::new(KeyCode::Char('R'), KeyModifiers::NONE)) + .unwrap(); + assert!(app.layout.is_records_mode(), "R should enter records mode"); + assert_eq!(app.layout.row_count(), 0, "fresh records view starts empty"); + + app.handle_key(KeyEvent::new(KeyCode::Char('o'), KeyModifiers::NONE)) + .unwrap(); + + assert_eq!( + app.layout.row_count(), + 1, + "o should create the first record row in an empty records view" + ); + assert!( + matches!(app.mode, AppMode::Editing { .. }), + "o should leave the app in edit mode, got {:?}", + app.mode + ); + } + #[test] fn command_mode_buffer_cleared_on_reentry() { use crossterm::event::KeyEvent;