tix-tracker 0.12.0

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

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Mode {
    Board,
    /// The batch modal (WP07). The modal's state is in [`App::bulk`]; the mode says
    /// only that it is open, exactly as `Mode::FilterModal` does.
    BulkModal,
    /// The triage inbox (WP06, T029). A modal over the board like settings and
    /// the batch modal, so the board, its tabs and its card cursor are all
    /// still there underneath and unchanged when it closes.
    ///
    /// The inbox's own cursor and selection live in [`App::triage`] rather than
    /// in [`BoardState`], which is what makes "the normal board/detail
    /// selection is unchanged when the inbox is not active" a property of
    /// which struct each cursor is in rather than something a test has to
    /// remember to restore.
    TriageInbox,
    /// The F3 Cycle mode (tui-cycle-mode): cycles grouped current / future /
    /// archived with statistics for the selected one. Like the triage inbox it
    /// is the mode's content, not a modal over the board; the
    /// planning-management surface is reached from it with `m`.
    Cycle,
    /// The all-issues mentions view (OpenSpec change `issue-mentions`): every
    /// pending `@mention` the local user holds, across issues. A modal over the
    /// board like the batch modal, so the cards stay on screen underneath.
    ///
    /// Its state is [`crate::ui::mentions::MentionsView`], held as
    /// [`App::mentions_view`] — `Some` exactly while it is open, so a closed view
    /// has no rows to act on.
    Mentions,
    /// The per-issue mention notice (OpenSpec change `issue-mentions`): the issue
    /// just opened carries unread mentions for the local user.
    ///
    /// Deliberately **non-mutating**: dismissing it (Esc, or `i` for "ignore")
    /// writes nothing, which is what makes the mention reappear the next time the
    /// issue is opened. Only "mark read" reaches the store. Its keys are raw (see
    /// `handle_key`), for the reason the recurring warning's are: the board binds
    /// some of the same letters and the binding layer would rewrite them first.
    MentionNotice,
    /// The recurring-issue due warning (mission `recurring-issues-01M3C7G7`,
    /// WP07): a non-mutating overlay over the board, `Some` exactly while it is
    /// open. Its state is [`crate::ui::recurring_warning::RecurringWarning`].
    ///
    /// The rows come from the shared detector, and each of the four answers is an
    /// explicit press — the overlay itself never generates, defers or ignores
    /// anything, which is what makes "detection is read-only" true in the TUI.
    /// Its keys are raw (see `handle_key`): the board's `d`/`g` bindings would
    /// rewrite them into `Esc`.
    RecurringWarning,
    /// The WIP override confirmation (mission `configurable-wip-limits-01M3C6WX`,
    /// WP05 T026): a blocking limit refused a move and the user is supplying the
    /// reason that authorizes it. An overlay over the board, like the due warning;
    /// it carries the one free-text field, so the reason is typed rather than
    /// pressed.
    WipOverride {
        /// The reason being typed.
        buffer: String,
    },
    /// The F7 Stats page's **Health tab** (mission
    /// `project-issue-health-status-01M3C8HX`, WP06): one row per scope, with its
    /// state, layer and derived value, and the diagnostics below. Read-only — the
    /// mutations live in the detail view's form.
    Stats,
    /// The health form (mission `project-issue-health-status-01M3C8HX`, WP06 T032):
    /// the audit fields a mutation needs, opened from the detail pane. An overlay
    /// over the pane, like the WIP override: it carries free text, so `Tab` walks the
    /// fields and `Enter` submits.
    HealthForm,
    /// The time-entry form (mission `per-issue-time-tracking-01M3C9A4`, WP05 T025):
    /// the audit fields an add, a correction or a deletion needs, opened from the `F7`
    /// Stats page's Time tab. An overlay over the page, like the health form: it
    /// carries free text, so `Tab` walks the fields and `Enter` submits.
    ///
    /// The form's own state is [`App::time_form`], and the tab underneath keeps
    /// drawing, so the entry rows the correction is about stay on screen while its
    /// fields are typed.
    TimeForm,
    /// The Time tab's filter editor (WP05 T026): the issue, assignee and period
    /// criteria the shared `TimeFilter` is built from. A separate mode only because it
    /// is a separate field set — the keys (`Tab`/`Enter`/`Esc`) are the time form's.
    TimeFilterForm,
    /// The F8 Schedule page (tix #1593): the recurring series and their state on its
    /// **Series** tab, and the configured cycle schedules with the cycles they
    /// generated on its **Cycle schedules** tab (mission
    /// `repeating-cycle-schedules-01M3C7W1`, tix #181), each with the keys that act
    /// on them. A top-level mode like `F4`/`F5`/`F6`.
    Schedule,
    /// The F6 Repos mode (tix #1529): the child (and parent) repositories this
    /// project coordinates. `j`/`k` walk the rows and `Enter` runs `tix` inside
    /// the selected repository. With none configured, the body explains how to
    /// add one under `[repos]`.
    Repos,
    /// The F11 Library page (tix #1593): the saved views and the issue/form
    /// templates that already exist, listed with the selected record's detail.
    /// `Tab` switches registries, `j`/`k` walk rows, and `Enter` applies the
    /// selected view to the board. Read-only apart from that apply — see
    /// [`crate::ui::library`].
    Library,
    /// The F10 **Portfolio** page (WP06): the project registry this mission
    /// introduces, listed with each project's derived member count and the selected
    /// project's record and members beside it. Read-only — see
    /// [`crate::ui::portfolio`].
    Portfolio,
    RolloverDialog,
    Detail,
    Form,
    ConfirmDelete,
    /// Archive or restore one issue, or every marked issue, after a `y`/`N`
    /// confirmation.
    ConfirmArchive {
        filenames: Vec<String>,
        restore: bool,
    },
    MoveIntoDone {
        buffer: String,
    },
    /// The blocked-move acknowledgement prompt (WP04, FR-004).
    ///
    /// Reached instead of applying a move toward the board's completion column
    /// when the issue has open blockers. `warning` is the **shared** text from
    /// `commands::blocked_note`, so the TUI says exactly what the CLI says.
    ConfirmBlockedMove {
        warning: String,
    },
    /// The reasoned-override prompt (WP05, FR-002, contract §3.3).
    ///
    /// Reached instead of applying a move that leaves a **terminal** category
    /// when the board declares a `category_map`. `reason` is the **shared**
    /// diagnostic from the service, so the TUI says exactly what the CLI says —
    /// parity includes the message, not only the accept/refuse decision.
    ///
    /// This prompt is the whole reason WP05 exists on this path. Until it, a
    /// terminal-exit transition was refused by the seam with a message naming
    /// `tix move … --reason`, and the TUI had **no way to supply one** — the flag
    /// is a CLI spelling and the TUI has no flags. So a user who moved an issue to
    /// `done` in the TUI could not move it back, in the TUI, at all.
    TransitionReason {
        /// The refusal being overridden, rendered verbatim.
        reason: String,
        /// What the user has typed so far.
        buffer: String,
    },
    Help {
        return_to: Box<Mode>,
    },
    /// The startup tips modal (tix #1632): one tip from the embedded
    /// [`crate::tips`] list, over the board opened by bare `tix`. `return_to` is
    /// the mode it was opened from, so closing it lands exactly where the session
    /// was headed.
    ///
    /// Its own state is [`App::tips`], `Some` exactly while it is open. Its keys
    /// are raw (see `handle_key`): the board binds some of the same letters, and
    /// the binding layer would rewrite them first.
    Tips {
        return_to: Box<Mode>,
    },
    /// The issue action menu (`Enter`, a plain left click, or right-click);
    /// `return_to` is the mode it was opened from. Activation runs the exact
    /// action the equivalent key binding would.
    ActionMenu {
        return_to: Box<Mode>,
    },
    Filter {
        buffer: String,
    },
    FilterModal,
    /// The saved-views modal (WP05, T024–T027). A modal over the board like
    /// settings and the batch modal, so the board, its tabs, and its card cursor
    /// are all still there underneath and unchanged when it closes.
    ///
    /// Its own state lives in [`App::views`] rather than in `BoardState`, so
    /// "navigation is unchanged when the view list is not open" is a property of
    /// which struct holds the cursor rather than something a test remembers to
    /// restore.
    Views,
    /// The F12 Config mode (tui-config-mode): the sectioned surface over
    /// dashboards, cycles, and milestones. Like the triage inbox and the cycle
    /// listing it is the mode's content, not a modal over the board. Its state
    /// is [`App::settings`], and its cycles/milestones sections drive
    /// [`App::planning`] too.
    Settings,
    PlanningManagement,
    PlanningForm,
    /// Calendar date picker popup; `return_to` is the modal it overlays.
    DatePicker {
        return_to: Box<Mode>,
    },
    Comment {
        buffer: String,
    },
    Asset {
        buffer: String,
    },
    /// The editor round trip left a file with a problem (tix #1506).
    ///
    /// One prompt for every post-edit problem: a file that will not parse, a
    /// file `tix lint` is unhappy about, or an edit that lost its title. It
    /// lists each problem with its diagnostic and a proposal, and offers the fix
    /// tix can apply, the editor again on the retained copy, an explicit
    /// declaration of an undeclared key, acknowledgement, and a discard that
    /// needs a second confirmation.
    ///
    /// Nothing is discarded automatically: the retained copy (`temp`) survives
    /// acknowledgement and is only removed by the confirmed discard. When
    /// `written` is true the edited content is already on disk (`tix lint`
    /// findings arrive after the write, tix #1510's #1506 behaviour), so the
    /// prompt is a report; when it is false nothing has been written and the fix
    /// writes the repaired file for the first time.
    ///
    /// `key` is the undeclared top-level frontmatter key the prompt can declare,
    /// `Some` only for an `unknown_key` problem with a non-empty value. It is what
    /// `confirm`/`declare_key` write into `tix.toml`; every other problem leaves
    /// it `None`, so those chords say there is nothing to declare.
    EditRepair {
        filename: String,
        /// `kind: message` of the first problem, for the status line and the
        /// prompt's heading.
        message: String,
        key: Option<String>,
        /// Every classified problem, in the order the diagnostics arrived.
        problems: Vec<EditProblem>,
        /// The retained edited copy — the editor's temp file when one exists.
        /// The only thing a confirmed discard removes.
        temp: Option<PathBuf>,
        /// Whether the edited content is already on disk.
        written: bool,
        /// A discard has been asked for once; the second keypress is the
        /// decision. `Esc` never sets it.
        discard_armed: bool,
    },
    /// The issue file changed on disk while the editor had it open (tix #1510).
    ///
    /// Entered **instead of** the write, so nothing about the raced edit is on
    /// disk: the newer file is untouched and the editor's version is still the
    /// temp copy. The three keys the prompt names are the only way out, and two of
    /// them — `a`/`Esc` (keep the on-disk version) and `c` (compare in the
    /// editor) — never write the editor's version at all. Only `o`/`y` overrides.
    EditConflict {
        /// The issue file, relative to the store's `issues_dir`.
        filename: String,
        /// The edited temp copy — the editor's version, kept until the user
        /// decides (abandoning removes it, overriding writes it, comparing
        /// reopens it).
        temp: PathBuf,
        /// The newer on-disk file, shown so the user can open it by hand.
        disk_path: PathBuf,
    },
    /// Create a relationship from the detail view (#1359), in two steps.
    ///
    /// `Step::Target` asks for the other issue as a **selector** — the same string
    /// `tix link <from> <to>` takes, so `#4`, `4`, or a title fragment all work and
    /// an unresolvable one produces the CLI's own error rather than a TUI-specific
    /// one. `Step::Kind` then asks for the kind, and it is **required**: C-001 forbids
    /// an untypeable relationship, so there is no default to fall back to and an
    /// empty or unrecognised answer is a refusal, not a guess.
    LinkCreate {
        buffer: String,
        step: LinkStep,
        /// The target resolved by the `Target` step, carried forward to `Kind`.
        ///
        /// Carried rather than re-resolved: the first step already applied the CLI's
        /// selector rules and reported its errors, and re-reading the buffer's
        /// meaning a second time would be a second place for the two to disagree. A
        /// `Kind` step with no target cannot happen — the mode is only entered in
        /// that state with one.
        target: Option<u32>,
    },
    /// Re-kind or retarget the relationship under the detail pane's cursor.
    ///
    /// Carries the target it was opened with so the kind step can submit an edit
    /// rather than a create: `tix link edit <id>` is an edit precisely because the
    /// stable id survives a re-kind, and a create-then-remove pair would lose it.
    LinkEdit {
        index: usize,
        buffer: String,
        step: LinkStep,
        /// The new target, when the user supplied one. `None` means **keep the
        /// existing target** and change only the kind — the same as `tix link edit
        /// <id> --type …` with no `--to`.
        target: Option<u32>,
        /// The new kind, when the user supplied one. `None` likewise means keep.
        kind: Option<crate::relationships::RelationKind>,
    },
    /// Confirm removing the relationship under the cursor.
    ///
    /// A relationship is stored in exactly one issue's file, so removing it is a
    /// write to another issue from the one on screen. It gets a confirmation for the
    /// same reason `ConfirmDelete` does, and the confirmation names the edge.
    ConfirmRemoveRelationship {
        index: usize,
    },
    /// Set, change or clear the open issue's parent (tix #1384).
    ///
    /// The detail pane's `P` opens it. Enter submits the typed selector and Esc
    /// cancels; an **empty** line clears the parent. The write goes through
    /// `Store::set_parent`/`Store::clear_parent`, the same hierarchy seam
    /// `tix edit --parent` uses, so one accepted change appends exactly one
    /// `parent_set`/`parent_changed`/`parent_cleared` History event — the TUI
    /// half of the CLI/TUI parity the create-only form could not express.
    ParentPrompt {
        buffer: String,
    },
}