tix-tracker 0.11.0

A terminal issue tracker: markdown + YAML frontmatter issues with a kanban TUI and CLI
Documentation
#[allow(clippy::wildcard_imports)]
use super::*;

impl App {
    pub(in crate::app) fn handle_confirm_delete_key(&mut self, key: KeyEvent) -> Result<Action> {
        match key.code {
            KeyCode::Char('y') | KeyCode::Char('Y') => self.confirm_delete_yes(),
            _ => self.confirm_delete_no(),
        }
    }

    /// Yes branch of the confirm-delete dialog — shared by the `y` key, the
    /// `Action::ConfirmDeleteYes` mouse hit (D6), and the run loop.
    pub(in crate::app) fn confirm_delete_yes(&mut self) -> Result<Action> {
        if let Some(filename) = self.confirm_delete_target.take() {
            self.store.delete(&filename)?;
            self.status_msg = Some(format!("Deleted {filename}"));
        }
        self.mode = Mode::Board;
        self.refresh_corpus()?;
        Ok(Action::None)
    }

    /// Cancel branch of the confirm-delete dialog — shared by every other key,
    /// `Action::ConfirmDeleteNo`, and the run loop.
    pub(in crate::app) fn confirm_delete_no(&mut self) -> Result<Action> {
        self.confirm_delete_target = None;
        self.mode = Mode::Board;
        Ok(Action::None)
    }

    /// The archive/restore confirmation. `y` runs the shared store operation;
    /// every other key cancels and writes nothing.
    pub(in crate::app) fn handle_confirm_archive_key(&mut self, key: KeyEvent) -> Result<Action> {
        let Mode::ConfirmArchive { filename, restore } = self.mode.clone() else {
            return Ok(Action::None);
        };
        if !matches!(key.code, KeyCode::Char('y') | KeyCode::Char('Y')) {
            self.mode = Mode::Board;
            self.status_msg = Some("archive/restore cancelled; nothing was written".to_string());
            return Ok(Action::None);
        }
        let outcome = if restore {
            self.store.restore(&filename)
        } else {
            self.store.archive(&filename)
        };
        self.mode = Mode::Board;
        match outcome {
            Ok(issue) => {
                self.status_msg = Some(if restore {
                    format!("Restored #{}", issue.number)
                } else {
                    format!("Archived #{}", issue.number)
                });
                self.refresh_corpus()?;
            }
            Err(error) => self.status_msg = Some(error.to_string()),
        }
        Ok(Action::None)
    }

    /// The blocked-move acknowledgement prompt (WP04, FR-004).
    ///
    /// Both branches go through the shared move, so the TUI's accept/reject
    /// decision, its diagnostic, and its History event are the *same code* the
    /// CLI runs. An unconfirmed move is not applied at all — the cancel branch
    /// changes nothing and returns to the board.
    pub(in crate::app) fn handle_confirm_blocked_move_key(
        &mut self,
        key: KeyEvent,
        warning: &str,
    ) -> Result<Action> {
        let confirmed = matches!(
            key.code,
            KeyCode::Char('y') | KeyCode::Char('Y') | KeyCode::Enter
        );
        if !confirmed {
            // Anything else cancels. Leaving the move unapplied is the safe
            // default: a prompt that guesses on an unrecognised key is a prompt
            // that can close blocked work by accident.
            self.finish_move();
            self.mode = Mode::Board;
            self.status_msg = Some(format!("Move cancelled; {warning}"));
            return Ok(Action::None);
        }

        if let Some(pending) = self.pending_move.as_mut() {
            pending.acknowledged = true;
        }
        self.mode = Mode::Board;
        self.advance_move()
    }

    pub(in crate::app) fn handle_rollover_dialog_key(&mut self, key: KeyEvent) -> Result<Action> {
        let Some(dialog) = self.rollover_dialog.as_mut() else {
            self.mode = Mode::Board;
            return Ok(Action::None);
        };
        match dialog.handle_key(key.code) {
            DialogEvent::ConfirmDo => {
                crate::commands::rollover::run(&self.store.config, &self.store, None, true)?;
                self.refresh_corpus()?;
                self.status_msg = Some("Rollover applied".to_string());
                self.rollover_dialog = None;
                self.mode = Mode::Board;
            }
            DialogEvent::ConfirmIgnore | DialogEvent::Dismiss => {
                self.rollover_dialog = None;
                self.mode = Mode::Board;
            }
            DialogEvent::None => {}
        }
        Ok(Action::None)
    }
}