tix/app/mode.rs
1#[allow(clippy::wildcard_imports)]
2use super::*;
3
4#[derive(Debug, Clone, PartialEq, Eq)]
5pub enum Mode {
6 Board,
7 /// The batch modal (WP07). The modal's state is in [`App::bulk`]; the mode says
8 /// only that it is open, exactly as `Mode::FilterModal` does.
9 BulkModal,
10 /// The triage inbox (WP06, T029). A modal over the board like settings and
11 /// the batch modal, so the board, its tabs and its card cursor are all
12 /// still there underneath and unchanged when it closes.
13 ///
14 /// The inbox's own cursor and selection live in [`App::triage`] rather than
15 /// in [`BoardState`], which is what makes "the normal board/detail
16 /// selection is unchanged when the inbox is not active" a property of
17 /// which struct each cursor is in rather than something a test has to
18 /// remember to restore.
19 TriageInbox,
20 /// The F3 Cycle mode (tui-cycle-mode): cycles grouped current / future /
21 /// archived with statistics for the selected one. Like the triage inbox it
22 /// is the mode's content, not a modal over the board; the
23 /// planning-management surface is reached from it with `m`.
24 Cycle,
25 /// The all-issues mentions view (OpenSpec change `issue-mentions`): every
26 /// pending `@mention` the local user holds, across issues. A modal over the
27 /// board like the batch modal, so the cards stay on screen underneath.
28 ///
29 /// Its state is [`crate::ui::mentions::MentionsView`], held as
30 /// [`App::mentions_view`] — `Some` exactly while it is open, so a closed view
31 /// has no rows to act on.
32 Mentions,
33 /// The per-issue mention notice (OpenSpec change `issue-mentions`): the issue
34 /// just opened carries unread mentions for the local user.
35 ///
36 /// Deliberately **non-mutating**: dismissing it (Esc, or `i` for "ignore")
37 /// writes nothing, which is what makes the mention reappear the next time the
38 /// issue is opened. Only "mark read" reaches the store. Its keys are raw (see
39 /// `handle_key`), for the reason the recurring warning's are: the board binds
40 /// some of the same letters and the binding layer would rewrite them first.
41 MentionNotice,
42 /// The recurring-issue due warning (mission `recurring-issues-01M3C7G7`,
43 /// WP07): a non-mutating overlay over the board, `Some` exactly while it is
44 /// open. Its state is [`crate::ui::recurring_warning::RecurringWarning`].
45 ///
46 /// The rows come from the shared detector, and each of the four answers is an
47 /// explicit press — the overlay itself never generates, defers or ignores
48 /// anything, which is what makes "detection is read-only" true in the TUI.
49 /// Its keys are raw (see `handle_key`): the board's `d`/`g` bindings would
50 /// rewrite them into `Esc`.
51 RecurringWarning,
52 /// The WIP override confirmation (mission `configurable-wip-limits-01M3C6WX`,
53 /// WP05 T026): a blocking limit refused a move and the user is supplying the
54 /// reason that authorizes it. An overlay over the board, like the due warning;
55 /// it carries the one free-text field, so the reason is typed rather than
56 /// pressed.
57 WipOverride {
58 /// The reason being typed.
59 buffer: String,
60 },
61 /// The F7 Stats page's **Health tab** (mission
62 /// `project-issue-health-status-01M3C8HX`, WP06): one row per scope, with its
63 /// state, layer and derived value, and the diagnostics below. Read-only — the
64 /// mutations live in the detail view's form.
65 Stats,
66 /// The health form (mission `project-issue-health-status-01M3C8HX`, WP06 T032):
67 /// the audit fields a mutation needs, opened from the detail pane. An overlay
68 /// over the pane, like the WIP override: it carries free text, so `Tab` walks the
69 /// fields and `Enter` submits.
70 HealthForm,
71 /// The time-entry form (mission `per-issue-time-tracking-01M3C9A4`, WP05 T025):
72 /// the audit fields an add, a correction or a deletion needs, opened from the `F7`
73 /// Stats page's Time tab. An overlay over the page, like the health form: it
74 /// carries free text, so `Tab` walks the fields and `Enter` submits.
75 ///
76 /// The form's own state is [`App::time_form`], and the tab underneath keeps
77 /// drawing, so the entry rows the correction is about stay on screen while its
78 /// fields are typed.
79 TimeForm,
80 /// The Time tab's filter editor (WP05 T026): the issue, assignee and period
81 /// criteria the shared `TimeFilter` is built from. A separate mode only because it
82 /// is a separate field set — the keys (`Tab`/`Enter`/`Esc`) are the time form's.
83 TimeFilterForm,
84 /// The F8 Schedule page (tix #1593): the recurring series and their state on its
85 /// **Series** tab, and the configured cycle schedules with the cycles they
86 /// generated on its **Cycle schedules** tab (mission
87 /// `repeating-cycle-schedules-01M3C7W1`, tix #181), each with the keys that act
88 /// on them. A top-level mode like `F4`/`F5`/`F6`.
89 Schedule,
90 /// The F6 Repos mode (tix #1529): the child (and parent) repositories this
91 /// project coordinates. `j`/`k` walk the rows and `Enter` runs `tix` inside
92 /// the selected repository. With none configured, the body explains how to
93 /// add one under `[repos]`.
94 Repos,
95 /// The F11 Library page (tix #1593): the saved views and the issue/form
96 /// templates that already exist, listed with the selected record's detail.
97 /// `Tab` switches registries, `j`/`k` walk rows, and `Enter` applies the
98 /// selected view to the board. Read-only apart from that apply — see
99 /// [`crate::ui::library`].
100 Library,
101 /// The F10 **Portfolio** page (WP06): the project registry this mission
102 /// introduces, listed with each project's derived member count and the selected
103 /// project's record and members beside it. Read-only — see
104 /// [`crate::ui::portfolio`].
105 Portfolio,
106 RolloverDialog,
107 Detail,
108 Form,
109 ConfirmDelete,
110 /// Archive or restore one issue, or every marked issue, after a `y`/`N`
111 /// confirmation.
112 ConfirmArchive {
113 filenames: Vec<String>,
114 restore: bool,
115 },
116 MoveIntoDone {
117 buffer: String,
118 },
119 /// The blocked-move acknowledgement prompt (WP04, FR-004).
120 ///
121 /// Reached instead of applying a move toward the board's completion column
122 /// when the issue has open blockers. `warning` is the **shared** text from
123 /// `commands::blocked_note`, so the TUI says exactly what the CLI says.
124 ConfirmBlockedMove {
125 warning: String,
126 },
127 /// The reasoned-override prompt (WP05, FR-002, contract §3.3).
128 ///
129 /// Reached instead of applying a move that leaves a **terminal** category
130 /// when the board declares a `category_map`. `reason` is the **shared**
131 /// diagnostic from the service, so the TUI says exactly what the CLI says —
132 /// parity includes the message, not only the accept/refuse decision.
133 ///
134 /// This prompt is the whole reason WP05 exists on this path. Until it, a
135 /// terminal-exit transition was refused by the seam with a message naming
136 /// `tix move … --reason`, and the TUI had **no way to supply one** — the flag
137 /// is a CLI spelling and the TUI has no flags. So a user who moved an issue to
138 /// `done` in the TUI could not move it back, in the TUI, at all.
139 TransitionReason {
140 /// The refusal being overridden, rendered verbatim.
141 reason: String,
142 /// What the user has typed so far.
143 buffer: String,
144 },
145 Help {
146 return_to: Box<Mode>,
147 },
148 /// The startup tips modal (tix #1632): one tip from the embedded
149 /// [`crate::tips`] list, over the board opened by bare `tix`. `return_to` is
150 /// the mode it was opened from, so closing it lands exactly where the session
151 /// was headed.
152 ///
153 /// Its own state is [`App::tips`], `Some` exactly while it is open. Its keys
154 /// are raw (see `handle_key`): the board binds some of the same letters, and
155 /// the binding layer would rewrite them first.
156 Tips {
157 return_to: Box<Mode>,
158 },
159 /// The issue action menu (`Enter`, a plain left click, or right-click);
160 /// `return_to` is the mode it was opened from. Activation runs the exact
161 /// action the equivalent key binding would.
162 ActionMenu {
163 return_to: Box<Mode>,
164 },
165 Filter {
166 buffer: String,
167 },
168 FilterModal,
169 /// The saved-views modal (WP05, T024–T027). A modal over the board like
170 /// settings and the batch modal, so the board, its tabs, and its card cursor
171 /// are all still there underneath and unchanged when it closes.
172 ///
173 /// Its own state lives in [`App::views`] rather than in `BoardState`, so
174 /// "navigation is unchanged when the view list is not open" is a property of
175 /// which struct holds the cursor rather than something a test remembers to
176 /// restore.
177 Views,
178 /// The F12 Config mode (tui-config-mode): the sectioned surface over
179 /// dashboards, cycles, and milestones. Like the triage inbox and the cycle
180 /// listing it is the mode's content, not a modal over the board. Its state
181 /// is [`App::settings`], and its cycles/milestones sections drive
182 /// [`App::planning`] too.
183 Settings,
184 PlanningManagement,
185 PlanningForm,
186 /// Calendar date picker popup; `return_to` is the modal it overlays.
187 DatePicker {
188 return_to: Box<Mode>,
189 },
190 Comment {
191 buffer: String,
192 },
193 Asset {
194 buffer: String,
195 },
196 /// The editor round trip left a file with a problem (tix #1506).
197 ///
198 /// One prompt for every post-edit problem: a file that will not parse, a
199 /// file `tix lint` is unhappy about, or an edit that lost its title. It
200 /// lists each problem with its diagnostic and a proposal, and offers the fix
201 /// tix can apply, the editor again on the retained copy, an explicit
202 /// declaration of an undeclared key, acknowledgement, and a discard that
203 /// needs a second confirmation.
204 ///
205 /// Nothing is discarded automatically: the retained copy (`temp`) survives
206 /// acknowledgement and is only removed by the confirmed discard. When
207 /// `written` is true the edited content is already on disk (`tix lint`
208 /// findings arrive after the write, tix #1510's #1506 behaviour), so the
209 /// prompt is a report; when it is false nothing has been written and the fix
210 /// writes the repaired file for the first time.
211 ///
212 /// `key` is the undeclared top-level frontmatter key the prompt can declare,
213 /// `Some` only for an `unknown_key` problem with a non-empty value. It is what
214 /// `confirm`/`declare_key` write into `tix.toml`; every other problem leaves
215 /// it `None`, so those chords say there is nothing to declare.
216 EditRepair {
217 filename: String,
218 /// `kind: message` of the first problem, for the status line and the
219 /// prompt's heading.
220 message: String,
221 key: Option<String>,
222 /// Every classified problem, in the order the diagnostics arrived.
223 problems: Vec<EditProblem>,
224 /// The retained edited copy — the editor's temp file when one exists.
225 /// The only thing a confirmed discard removes.
226 temp: Option<PathBuf>,
227 /// Whether the edited content is already on disk.
228 written: bool,
229 /// A discard has been asked for once; the second keypress is the
230 /// decision. `Esc` never sets it.
231 discard_armed: bool,
232 },
233 /// The issue file changed on disk while the editor had it open (tix #1510).
234 ///
235 /// Entered **instead of** the write, so nothing about the raced edit is on
236 /// disk: the newer file is untouched and the editor's version is still the
237 /// temp copy. The three keys the prompt names are the only way out, and two of
238 /// them — `a`/`Esc` (keep the on-disk version) and `c` (compare in the
239 /// editor) — never write the editor's version at all. Only `o`/`y` overrides.
240 EditConflict {
241 /// The issue file, relative to the store's `issues_dir`.
242 filename: String,
243 /// The edited temp copy — the editor's version, kept until the user
244 /// decides (abandoning removes it, overriding writes it, comparing
245 /// reopens it).
246 temp: PathBuf,
247 /// The newer on-disk file, shown so the user can open it by hand.
248 disk_path: PathBuf,
249 },
250 /// Create a relationship from the detail view (#1359), in two steps.
251 ///
252 /// `Step::Target` asks for the other issue as a **selector** — the same string
253 /// `tix link <from> <to>` takes, so `#4`, `4`, or a title fragment all work and
254 /// an unresolvable one produces the CLI's own error rather than a TUI-specific
255 /// one. `Step::Kind` then asks for the kind, and it is **required**: C-001 forbids
256 /// an untypeable relationship, so there is no default to fall back to and an
257 /// empty or unrecognised answer is a refusal, not a guess.
258 LinkCreate {
259 buffer: String,
260 step: LinkStep,
261 /// The target resolved by the `Target` step, carried forward to `Kind`.
262 ///
263 /// Carried rather than re-resolved: the first step already applied the CLI's
264 /// selector rules and reported its errors, and re-reading the buffer's
265 /// meaning a second time would be a second place for the two to disagree. A
266 /// `Kind` step with no target cannot happen — the mode is only entered in
267 /// that state with one.
268 target: Option<u32>,
269 },
270 /// Re-kind or retarget the relationship under the detail pane's cursor.
271 ///
272 /// Carries the target it was opened with so the kind step can submit an edit
273 /// rather than a create: `tix link edit <id>` is an edit precisely because the
274 /// stable id survives a re-kind, and a create-then-remove pair would lose it.
275 LinkEdit {
276 index: usize,
277 buffer: String,
278 step: LinkStep,
279 /// The new target, when the user supplied one. `None` means **keep the
280 /// existing target** and change only the kind — the same as `tix link edit
281 /// <id> --type …` with no `--to`.
282 target: Option<u32>,
283 /// The new kind, when the user supplied one. `None` likewise means keep.
284 kind: Option<crate::relationships::RelationKind>,
285 },
286 /// Confirm removing the relationship under the cursor.
287 ///
288 /// A relationship is stored in exactly one issue's file, so removing it is a
289 /// write to another issue from the one on screen. It gets a confirmation for the
290 /// same reason `ConfirmDelete` does, and the confirmation names the edge.
291 ConfirmRemoveRelationship {
292 index: usize,
293 },
294 /// Set, change or clear the open issue's parent (tix #1384).
295 ///
296 /// The detail pane's `P` opens it. Enter submits the typed selector and Esc
297 /// cancels; an **empty** line clears the parent. The write goes through
298 /// `Store::set_parent`/`Store::clear_parent`, the same hierarchy seam
299 /// `tix edit --parent` uses, so one accepted change appends exactly one
300 /// `parent_set`/`parent_changed`/`parent_cleared` History event — the TUI
301 /// half of the CLI/TUI parity the create-only form could not express.
302 ParentPrompt {
303 buffer: String,
304 },
305}