sql-cli 1.73.1

SQL query tool for CSV/JSON with both interactive TUI and non-interactive CLI modes - perfect for exploration and automation
Documentation
# Parallel State Manager: Add Without Breaking

## The Problem
Even a "shell" state manager requires changing all 57 `set_mode()` calls. That's a big bang!

## The Solution: Run in Parallel
Add the StateManager **alongside** existing state, not replacing it. The existing code continues to work while we observe and learn.

## Phase 1: Shadow State Manager

```rust
/// Shadows the existing state system - doesn't control anything yet
pub struct ShadowStateManager {
    state: AppState,
    history: VecDeque<(Instant, AppState, String)>,
    
    // Track discrepancies between our state and actual state
    discrepancies: Vec<String>,
}

impl ShadowStateManager {
    /// Called AFTER the existing set_mode() - just observes
    pub fn observe_mode_change(&mut self, mode: AppMode, trigger: &str) {
        let new_state = self.mode_to_state(mode);
        
        info!(target: "shadow_state", 
            "Observed: {:?} -> {:?} ({})", 
            self.state, new_state, trigger
        );
        
        self.state = new_state;
        self.history.push_back((Instant::now(), self.state.clone(), trigger.to_string()));
    }
    
    /// Called when we observe search state changes
    pub fn observe_search_start(&mut self, search_type: &str) {
        info!(target: "shadow_state", "Observed search start: {}", search_type);
        // Update our shadow state based on observation
    }
    
    /// Check if our state matches reality
    pub fn verify_state(&mut self, actual: &ActualState) {
        if !self.matches_actual(actual) {
            let msg = format!(
                "State mismatch! Shadow: {:?}, Actual: {:?}", 
                self.state, actual
            );
            warn!(target: "shadow_state", "{}", msg);
            self.discrepancies.push(msg);
        }
    }
    
    /// Get what we THINK the state should be
    pub fn predicted_state(&self) -> &AppState {
        &self.state
    }
}
```

## Integration: Minimal Touch Points

### Step 1: Add to EnhancedTuiApp
```rust
pub struct EnhancedTuiApp {
    // ... existing fields unchanged
    
    #[cfg(feature = "shadow-state")]
    shadow_state: ShadowStateManager, // NEW - only when testing
}
```

### Step 2: Add observation calls (not replacement!)
```rust
// EXISTING CODE UNCHANGED:
self.buffer_mut().set_mode(AppMode::Results);

// ADD AFTER (doesn't affect existing):
#[cfg(feature = "shadow-state")]
self.shadow_state.observe_mode_change(AppMode::Results, "execute_query");
```

### Step 3: Add verification in render
```rust
fn render_status_line(&self, f: &mut Frame, area: Rect) {
    // ... existing rendering code ...
    
    #[cfg(feature = "shadow-state")]
    {
        // Show shadow state in status line for comparison
        let shadow_display = format!("[Shadow: {}]", 
            self.shadow_state.predicted_state());
        // Render it in corner for debugging
    }
}
```

## Even More Incremental: Wrapper Pattern

```rust
/// Wraps existing buffer to intercept state changes
pub struct StateTrackingBuffer<'a> {
    inner: &'a mut Buffer,
    state_tracker: &'a mut ShadowStateManager,
}

impl<'a> StateTrackingBuffer<'a> {
    pub fn set_mode(&mut self, mode: AppMode) {
        // Call the real implementation
        self.inner.set_mode(mode.clone());
        
        // Track the state change
        self.state_tracker.observe_mode_change(mode, "wrapped_call");
    }
    
    // Delegate everything else unchanged
    pub fn get_mode(&self) -> AppMode {
        self.inner.get_mode()
    }
}

// Usage - wrap only where we want to observe:
let mut tracking_buffer = StateTrackingBuffer {
    inner: self.buffer_mut(),
    state_tracker: &mut self.shadow_state,
};
tracking_buffer.set_mode(AppMode::Results);
```

## Incremental Migration Path

### Phase 1: Pure Observation (No Risk!)
1. Add `ShadowStateManager` with feature flag
2. Add ~5 observation points at key locations:
   - Execute query
   - Start search  
   - Exit search
   - Return to command
   - Switch modes
3. Run and observe logs - learn the patterns
4. No functionality changes - can't break anything!

### Phase 2: Verification (Find Discrepancies)
1. Add verification checks
2. Compare shadow state with actual state
3. Log mismatches to understand missing transitions
4. Still no functionality changes

### Phase 3: Single Source Experiment
1. Pick ONE feature (like the N key mapping)
2. Use shadow state for just that decision:
```rust
// Just ONE place uses the new state:
let should_search = if cfg!(feature = "use-shadow-state") {
    self.shadow_state.is_search_active()  // NEW
} else {
    // Existing 3-way check
    !buffer.get_search_pattern().is_empty() 
        || self.vim_search_manager.borrow().is_active()
        || self.state_container.column_search().is_active
};
```
3. If it works, expand usage
4. If not, feature flag off!

### Phase 4: Gradual Takeover
1. One by one, switch decisions to use shadow state
2. Each behind a feature flag initially
3. Once all decisions use shadow state, make it primary
4. Remove old state code

## Actual Starting Code

```rust
// src/ui/shadow_state.rs - NEW FILE
use crate::buffer::AppMode;

#[derive(Debug, Clone)]
pub enum AppState {
    Command,
    Results,
    Search,
    // Start simple!
}

pub struct ShadowStateManager {
    state: AppState,
    transition_count: usize,
}

impl ShadowStateManager {
    pub fn new() -> Self {
        Self {
            state: AppState::Command,
            transition_count: 0,
        }
    }
    
    pub fn observe(&mut self, mode: AppMode) {
        let new_state = match mode {
            AppMode::Command => AppState::Command,
            AppMode::Results => AppState::Results,
            AppMode::Search | AppMode::ColumnSearch => AppState::Search,
            _ => return, // Ignore others for now
        };
        
        if !matches!((&self.state, &new_state), (AppState::Search, AppState::Search)) {
            self.transition_count += 1;
            info!(target: "shadow", 
                "[#{}] {} -> {}", 
                self.transition_count,
                format!("{:?}", self.state),
                format!("{:?}", new_state)
            );
        }
        
        self.state = new_state;
    }
    
    pub fn is_search(&self) -> bool {
        matches!(self.state, AppState::Search)
    }
}
```

Then add just ONE line after existing code:
```rust
self.buffer_mut().set_mode(AppMode::Results);
#[cfg(feature = "shadow-state")]
self.shadow_state.observe(AppMode::Results);  // Just observe!
```

## Benefits of This Approach

1. **Zero Risk**: Observation doesn't change behavior
2. **Learn First**: Understand patterns before changing
3. **Feature Flags**: Turn off instantly if issues
4. **Incremental**: Each step is tiny and reversible
5. **Parallel Running**: Old and new side by side

## The Absolutely Minimal Start

1. Create `shadow_state.rs` with basic enum
2. Add `ShadowStateManager` to app struct (feature flagged)
3. Add ONE observe call after execute_query
4. Log and watch
5. Add one more observe call
6. Repeat

This way we never need a big bang - we're just adding logging alongside existing code!