sql-cli 1.67.2

SQL query tool for CSV/JSON with both interactive TUI and non-interactive CLI modes - perfect for exploration and automation
Documentation
# Practical State Migration Plan: AppStateContainer → Buffer

## State Categories Analysis

### Category 1: Buffer-Persistent State (MUST migrate to Buffer)
These must be saved for perfect buffer restoration:

1. **Query & Data State**
   - `command_input` - Query text and cursor position
   - `results` - DataView with query results
   - `results_cache` - Cached query results

2. **View Configuration**
   - `column_search` - Which columns are visible/searched
   - `filter` - Active filters on data
   - `sort` - Sort configuration per column
   - `selection` - Selected rows/cells
   - `navigation` - Cursor position in results
   - `scroll` - Viewport position

3. **Search States**
   - `search` - Vim search pattern and matches
   - `history_search` - History search state

4. **Mode State**
   - `mode_stack` - Current mode and mode history

### Category 2: TUI-Transient State (can stay in AppStateContainer)
These are UI-only and don't need buffer persistence:

1. **UI Widgets**
   - `widgets` - Help, stats, debug widget states
   - `column_stats` - Temporary stats display
   - `jump_to_row` - Temporary jump dialog

2. **History & Debug**
   - `command_history` - Shared across all buffers
   - `key_press_history` - Debug/diagnostic only

3. **System State**
   - `clipboard` - System clipboard integration
   - `chord` - Key chord detection (UI event handling)
   - `completion` - Tab completion state (regenerated on demand)

4. **Undo/Redo**
   - `undo_redo` - Could be either, depends on requirements

## Migration Strategy

### Phase 1: Create BufferState Struct (Week 1)
```rust
// In buffer.rs
pub struct BufferState {
    // Start with most critical state
    pub query: QueryState,
    pub data: DataState,
    pub view: ViewState,
}

pub struct QueryState {
    pub text: String,
    pub cursor_position: usize,
    pub last_executed: Option<String>,
}

pub struct DataState {
    pub data_view: Option<DataView>,
    pub cache_key: Option<String>,
}

pub struct ViewState {
    pub cursor: (usize, usize),
    pub viewport: ViewportState,
    pub columns: ColumnState,
    pub filters: FilterState,
}
```

### Phase 2: Dual-Write Pattern (Week 2)
Keep AppStateContainer working while gradually migrating:

```rust
impl AppStateContainer {
    // Start writing to both places
    pub fn set_query(&mut self, query: String) {
        // Old location (keep working)
        self.command_input.borrow_mut().text = query.clone();
        
        // New location (build up)
        self.get_current_buffer()
            .borrow_mut()
            .state
            .query
            .text = query;
    }
    
    // Gradually switch reads to Buffer
    pub fn get_query(&self) -> String {
        // During migration: read from Buffer if available
        if let Some(buffer_query) = self.try_get_from_buffer() {
            buffer_query
        } else {
            // Fallback to old location
            self.command_input.borrow().text.clone()
        }
    }
}
```

### Phase 3: Component by Component Migration

#### Step 1: DataView (Highest Priority)
```rust
// Move DataView ownership to Buffer
impl Buffer {
    pub fn set_data_view(&mut self, view: DataView) {
        self.state.data.data_view = Some(view);
        self.state.data.version += 1;
    }
}

// AppStateContainer becomes a pass-through
impl AppStateContainer {
    pub fn get_data_view(&self) -> Option<&DataView> {
        self.get_current_buffer()
            .borrow()
            .state
            .data
            .data_view
            .as_ref()
    }
}
```

#### Step 2: Search States
```rust
// Unify all search states in Buffer
pub struct SearchState {
    pub vim: Option<VimSearchState>,
    pub column: Option<ColumnSearchState>,
    pub fuzzy: Option<String>,
    pub data: Option<String>,
}

// Migrate VimSearchManager to use Buffer
impl VimSearchManager {
    pub fn update(&mut self, buffer: &Buffer) {
        if let Some(vim_state) = &buffer.state.search.vim {
            self.pattern = vim_state.pattern.clone();
            self.matches = vim_state.matches.clone();
        }
    }
}
```

#### Step 3: View Configuration
Move column visibility, sorting, filtering to Buffer.

### Phase 4: Remove from AppStateContainer
Once dual-write is stable:
1. Remove state fields from AppStateContainer
2. Convert AppStateContainer methods to dispatch actions
3. Make AppStateContainer purely a coordinator

## Implementation Checklist

### Week 1: Foundation
- [ ] Create BufferState struct hierarchy
- [ ] Add state field to Buffer
- [ ] Implement state versioning
- [ ] Add debug logging for state changes

### Week 2: DataView Migration
- [ ] Move DataView to BufferState
- [ ] Dual-write DataView updates
- [ ] Switch reads to Buffer
- [ ] Test buffer switching preserves DataView
- [ ] Remove DataView from AppStateContainer

### Week 3: Search State Consolidation
- [ ] Create unified SearchState in Buffer
- [ ] Migrate VimSearchManager to read from Buffer
- [ ] Move column search to Buffer
- [ ] Test N key issue is fixed
- [ ] Remove search states from AppStateContainer

### Week 4: View State Migration
- [ ] Move viewport/cursor to Buffer
- [ ] Move column configuration to Buffer
- [ ] Move filter state to Buffer
- [ ] Test full view restoration on buffer switch

### Week 5: Cleanup
- [ ] Remove migrated fields from AppStateContainer
- [ ] Convert AppStateContainer to dispatcher pattern
- [ ] Update all components to read from Buffer
- [ ] Add comprehensive tests

## Testing Strategy

Create test to verify buffer switching:
```rust
#[test]
fn test_buffer_state_restoration() {
    // Setup buffer 1 with specific state
    let buffer1 = create_test_buffer();
    buffer1.state.query.text = "SELECT * FROM users";
    buffer1.state.data.data_view = Some(test_data_view());
    buffer1.state.view.cursor = (5, 10);
    
    // Switch to buffer 2
    let buffer2 = create_test_buffer();
    buffer2.state.query.text = "SELECT * FROM orders";
    
    // Switch back to buffer 1
    // Assert all state is restored
    assert_eq!(buffer1.state.query.text, "SELECT * FROM users");
    assert_eq!(buffer1.state.view.cursor, (5, 10));
    assert!(buffer1.state.data.data_view.is_some());
}
```

## Success Criteria

1. **Buffer switching works perfectly** - All visual state restored
2. **N key issue fixed** - Search states properly managed
3. **No duplicate state** - Single source of truth
4. **Performance maintained** - No excessive cloning
5. **Backwards compatible** - Gradual migration without breakage

## Key Principles

1. **Dual-write during migration** - Keep both systems working
2. **Migrate by feature** - DataView first, then search, then view
3. **Test each migration** - Verify buffer switching works
4. **Version everything** - Avoid unnecessary re-renders
5. **Keep transient state separate** - Not everything goes in Buffer

## What NOT to Migrate

Keep these in AppStateContainer or TUI:
- Widget states (help, debug panels)
- Key press history (diagnostic only)
- Chord detection (event handling)
- System clipboard (OS integration)
- Command history (shared across buffers)