kanban-persistence 0.2.0

Persistence layer for the kanban project management tool with progressive saving and multi-instance support
Documentation
# kanban-persistence

Persistence layer for the kanban project management tool. Handles JSON storage, format versioning, and data migration.

## Installation

Add to your `Cargo.toml`:

```toml
[dependencies]
kanban-persistence = { path = "../kanban-persistence" }
```

## Features

### Progressive Auto-Save

- **Dirty Flag Tracking**: Changes are marked and queued for persistence
- **Debounced Saving**: 500ms minimum interval between disk writes to prevent excessive I/O
- **Atomic Writes**: Temporary file writes with atomic rename for crash safety
- **Command Audit Log**: All commands are tracked for audit trails

### Format Versioning

- **V2 JSON Format**: Structured format with metadata and version tracking
- **Automatic V1→V2 Migration**: Legacy files are transparently upgraded on first load
- **Backup Creation**: V1 files backed up as `.v1.backup` before migration
- **Version Detection**: Automatic format detection without user intervention

### Multi-Instance Support

- **Instance IDs**: Each application instance has a unique ID for coordination
- **Last-Write-Wins**: Concurrent modifications resolved by latest timestamp
- **File Watching**: Detects external changes for reload prompts
- **Conflict Resolution**: Automatic merging strategies for safe concurrent access

## API Reference

### JsonFileStore

Main persistence store implementation:

```rust
use kanban_persistence::{JsonFileStore, PersistenceStore};

// Create store
let store = JsonFileStore::new("board.json");

// Get instance ID
let instance_id = store.instance_id();

// Save data
let snapshot = StoreSnapshot {
    data: serde_json::to_vec(&data)?,
    metadata: PersistenceMetadata::new(instance_id),
};
store.save(snapshot).await?;

// Load data (automatically migrates V1 to V2)
let (snapshot, metadata) = store.load().await?;
```

### StateManager (kanban-tui)

Manages state mutations and persistence:

```rust
use kanban_tui::state::StateManager;
use kanban_domain::commands::Command;

let mut manager = StateManager::new(Some("board.json".into()));

// Execute command (sets dirty flag)
manager.execute_with_context(
    &mut boards,
    &mut columns,
    &mut cards,
    &mut sprints,
    &mut archived_cards,
    Box::new(CreateCard { /* ... */ }),
)?;

// Periodically save (respects 500ms debounce)
manager.save_if_needed(&snapshot).await?;

// Force save immediately (bypasses debounce)
manager.save_now(&snapshot).await?;
```

## Architecture

```
kanban-core
    └── kanban-domain
         └── kanban-persistence
              └── kanban-tui (StateManager uses persistence)
```

### Command Pattern Flow

1. **Event Handler** collects data and creates Command
2. **Command** is executed via StateManager::execute_command()
3. **CommandContext** applies mutation to data
4. **Dirty Flag** is set by StateManager
5. **Periodic Timer** calls save_if_needed()
6. **Debounce Check** ensures 500ms minimum interval
7. **Atomic Write** saves to disk with temp file + rename

### Data Flow

```
User Input
Event Handler
Command Creation
StateManager::execute_command()
CommandContext::execute()
Data Mutation
Dirty Flag = true
[500ms timer]
StateManager::save_if_needed()
JsonFileStore::save()
Atomic Write
Disk (persisted)
```

## Format Specification

### V2 Format

```json
{
  "version": 2,
  "metadata": {
    "instance_id": "uuid-here",
    "saved_at": "2024-01-15T10:30:00Z"
  },
  "data": {
    "boards": [],
    "columns": [],
    "cards": [],
    "sprints": [],
    "archived_cards": []
  }
}
```

### V1 Format (Deprecated)

Legacy format without version field or metadata:

```json
{
  "boards": [],
  "columns": [],
  "cards": [],
  "sprints": []
}
```

Migration automatically adds metadata and wraps data.

## Migration Strategy

### Automatic V1→V2 Migration

1. **Detection**: `Migrator::detect_version()` checks for `version` field
2. **Backup**: Original V1 file copied to `.v1.backup`
3. **Transform**: Data wrapped with V2 metadata
4. **Write**: Migrated file written atomically
5. **Logging**: Migration progress logged for user visibility

### Manual Migration

```rust
use kanban_persistence::migration::{Migrator, FormatVersion};

// Detect current version
let version = Migrator::detect_version("board.json").await?;

// Migrate if needed
if version == FormatVersion::V1 {
    Migrator::migrate(FormatVersion::V1, FormatVersion::V2, "board.json").await?;
}
```

## Performance Characteristics

### Debouncing Benefits

- **Reduced I/O**: Prevents disk thrashing during rapid edits
- **Better Responsiveness**: 500ms debounce balances persistence with UI responsiveness
- **Predictable Load**: Steady-state save frequency ~2 saves/second maximum

### Atomic Write Safety

- **Crash Safety**: Incomplete writes cannot corrupt file
- **Two-Phase Commit**: Write to temp, then atomic rename
- **Recovery**: Interrupted writes leave original file intact

## Examples

### Setting up Progressive Save

```rust
use kanban_tui::state::StateManager;
use tokio::time::{interval, Duration};

let mut manager = StateManager::new(Some("board.json".into()));

// Periodic save task (runs in background)
tokio::spawn(async move {
    let mut save_interval = interval(Duration::from_millis(100));

    loop {
        save_interval.tick().await;

        // Respects 500ms debounce internally
        if let Err(e) = manager.save_if_needed(&snapshot).await {
            tracing::error!("Failed to save: {}", e);
        }
    }
});
```

### Handling Concurrent Modifications

```rust
// When file is modified externally (multi-instance editing)
// JsonFileStore detects the change via file watching
// Application can prompt user for reload with conflict resolution
// Last-write-wins strategy automatically applied
```

## Error Handling

All public APIs return `KanbanResult<T>`:

```rust
use kanban_persistence::JsonFileStore;

match store.load().await {
    Ok((snapshot, metadata)) => {
        // Handle loaded data
    }
    Err(e) => {
        // Could be serialization error, missing file, or version error
        eprintln!("Failed to load: {}", e);
    }
}
```

## Dependencies

- `kanban-core` - Foundation types and traits
- `kanban-domain` - Domain models
- `serde`, `serde_json` - Serialization
- `tokio` - Async runtime
- `uuid` - ID generation
- `chrono` - Timestamps
- `async-trait` - Async trait support
- `thiserror` - Error handling
- `notify` - File watching

## License

Apache 2.0 - See [LICENSE.md](../../LICENSE.md) for details