aven 0.1.6

Local-first task manager CLI and sync server
Documentation
use anyhow::Result;

use crate::operations::{resolve_conflict, task_conflicts};
use crate::tui::store::{ConflictTarget, MutationMessage};
use crate::undo::{UndoCommand, task_field_value};

use super::TuiStore;

impl TuiStore {
    pub(crate) async fn conflict_targets(
        &self,
        index: Option<usize>,
    ) -> Result<Option<Vec<ConflictTarget>>> {
        let Some(item) = self.selected_task(index) else {
            return Ok(None);
        };
        self.activate_workspace();
        let mut conn = self.pool.acquire().await?;
        let details = task_conflicts(&mut conn, &item.task.id, None).await?;
        Ok(Some(
            details
                .into_iter()
                .map(|detail| ConflictTarget {
                    task_id: item.task.id.clone(),
                    display_ref: item.display_ref.clone(),
                    field: detail.field,
                    variant_a: detail.variant_a,
                    local_value: detail.local_value,
                    variant_b: detail.variant_b,
                    remote_value: detail.remote_value,
                })
                .collect(),
        ))
    }

    pub(crate) async fn resolve_conflict_value(
        &mut self,
        target: ConflictTarget,
        value: String,
    ) -> Result<MutationMessage> {
        self.activate_workspace();
        let workspace_id = self.active_workspace.id.clone();
        let mut conn = self.pool.acquire().await?;
        let before =
            task_field_value(&mut conn, &workspace_id, &target.task_id, &target.field).await?;
        let conflict_id =
            crate::undo::conflict_row_id(&mut conn, &workspace_id, &target.task_id, &target.field)
                .await?;
        let outcome = resolve_conflict(&mut conn, &target.task_id, &target.field, &value).await?;
        let after =
            task_field_value(&mut conn, &workspace_id, &target.task_id, &target.field).await?;
        drop(conn);
        self.record_undo_commands(
            &format!("conflict {} {}", target.display_ref, target.field),
            vec![UndoCommand::RestoreConflictResolution {
                task_id: target.task_id.clone(),
                field: target.field.clone(),
                before,
                after,
                conflict_id,
            }],
        )
        .await?;
        self.refresh_task_message(
            &outcome.task.id,
            format!(
                "resolved {} conflict field={}",
                target.display_ref, outcome.field
            ),
        )
        .await
    }

    pub(crate) fn next_conflict_flag_index(
        flags: &[bool],
        selected: Option<usize>,
        delta: isize,
    ) -> Option<usize> {
        if flags.is_empty() || !flags.iter().any(|flag| *flag) {
            return None;
        }
        let len = flags.len() as isize;
        let mut current = selected.unwrap_or(0).min(flags.len() - 1) as isize;
        for _ in 0..len {
            current = (current + delta).rem_euclid(len);
            if flags[current as usize] {
                return Some(current as usize);
            }
        }
        None
    }

    pub(crate) fn next_conflict_index(
        &self,
        selected: Option<usize>,
        delta: isize,
    ) -> Option<usize> {
        let flags = self
            .tasks
            .iter()
            .map(|task| task.has_conflict)
            .collect::<Vec<_>>();
        Self::next_conflict_flag_index(&flags, selected, delta)
    }
}