1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
//! Modal key handlers for the `tm projects` TUI (#2118/#2383/#2120).
//!
//! Why: hoisted out of `events/mod.rs` pre-emptively (per CLAUDE.md's
//! SLOC-cap convention: "one public module per logical concept, a thin
//! `mod.rs` that re-exports, and sibling files with clear single
//! responsibilities") so adding the #2120 config-form key handler does not
//! push `events/mod.rs` over the 500-SLOC production cap. Each of this
//! screen's three modals (confirm gate, Deliverable/Milestone view, config
//! form) shares the identical "captures ALL input while open" discipline —
//! grouping their handlers here keeps that shared shape visible in one file
//! separate from the outer per-pane dispatch [`super::handle_key`] owns.
//! What: [`handle_confirm_key`] (DOC-35 §5.2), [`handle_deliverable_view_key`]
//! (DOC-35 §10.8, #2383), and [`handle_config_form_key`] (DOC-35 §6, #2120) —
//! each called from [`super::handle_key`] once its matching `Option` field on
//! [`ProjectCtlState`] is confirmed `Some`.
//! Test: `super::tests` (in `events/mod.rs`) exercises all three exclusively
//! through the public [`super::handle_key`] entry point, matching how the
//! real run loop calls it — there is no separate test module here, since
//! these functions have no meaningful behavior independent of that dispatch.
use ;
use PendingAction;
use crate;
/// Resolve the currently open confirmation gate against one key event.
///
/// Why: split out of [`super::handle_key`] so the "confirm gate captures ALL
/// input" contract (DOC-35 §5.2, plus the spec's "`q`/`Ctrl-C` not in a
/// modal" / "`Esc` any modal/form" rules) lives in one place, independent of
/// whatever pane happened to have focus when the gate opened.
/// What: `y`/`Y`/Enter → clears the gate and returns the confirmed
/// [`PendingAction`] (`Kill` or `Decommission`, per [`ConfirmKind`]);
/// `n`/`N`/Esc → clears the gate, sets a "cancelled" notice, returns `None`;
/// any other key (including `q`/Ctrl-C) → the gate stays open, returns
/// `None` — a destructive action can only ever be resolved by an explicit
/// yes/no, never dismissed as a side effect of an unrelated keypress.
pub
/// Map a resolved [`PendingConfirm`] to its confirmed [`PendingAction`].
/// Route one key event while the Deliverable/Milestone view is open (DOC-35
/// §10.8, #2383).
///
/// Why: split out of [`super::handle_key`] for the same reason
/// [`handle_confirm_key`] is — the "this modal captures ALL input" contract
/// lives in one place. Unlike the confirm gate (which resolves to an action
/// on `y`/`n`), this view is read-only, so every branch returns `None` — it
/// never produces a [`PendingAction`].
/// What: `↑`/`↓` scroll the body by one line; `Esc` or `v` closes the view;
/// every other key (including `q`/`Ctrl-C`) is swallowed, matching the
/// confirm gate's "not in a modal" quit rule.
/// Test: `super::tests::deliverable_view_*` / `super::tests::v_*`.
pub
/// Route one key event while the config form is open (DOC-35 §6, #2120).
///
/// Why: split out of [`super::handle_key`] for the same "modal captures ALL
/// input" reason as the other two handlers in this module — but UNLIKE
/// them, this modal is editable, so most keys mutate a field buffer rather
/// than being swallowed outright.
/// What: `Esc` closes WITHOUT submitting (discards unsaved edits — matches
/// every other modal's Esc convention). `Tab`/`Shift+Tab` cycle the focused
/// field (scoped to the form — the outer pane-focus Tab is unreachable here,
/// see the module doc on [`super`]). `Backspace` removes the last character
/// of the focused field's buffer. `Enter` submits (see
/// [`submit_config_form`] — chosen because Enter has no other meaning while
/// this modal is open: fields are single-line buffers navigated via Tab, not
/// newline-accepting, so there is no ambiguity with "insert a newline").
/// Every OTHER key, including a bare `q`/`Ctrl-C` that would otherwise quit,
/// is treated as a typed character and appended to the focused field's
/// buffer — deliberately, matching "modal captures ALL input": an operator
/// setting `gh_user` to a value containing 'q' must not have that keystroke
/// misinterpreted as quit.
/// Test: `super::tests::config_form_*`.
pub
/// Build the [`PendingAction::SubmitConfig`] for the open config form's
/// current edits, or reject the submit inline when nothing changed.
///
/// Why: the single seam [`handle_config_form_key`]'s `Enter` arm calls;
/// keeping the "diff, then either error-inline or return an action" logic
/// here (rather than inline in the match arm) keeps that arm one line, like
/// every other [`super::handle_key`] verb.
/// What: `None` config form (caller error — [`handle_config_form_key`] only
/// calls this when `state.config_form.is_some()`) or an unedited form (empty
/// diff) → [`ProjectCtlState::set_config_form_error`], returns `None` (stays
/// open). An edited form → [`crate::project_config::merge_patch_args`] over
/// the diff, returns `Some(PendingAction::SubmitConfig)`; the form stays
/// open until [`super::super::actions::dispatch`] resolves it (closes on
/// success, sets the inline error on failure — never closed here).