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
//! `tm projects` 4-pane TUI skeleton — the project control plane (#2118, DOC-35 §5).
//!
//! Why: operators managing several projects' worth of managed sessions need
//! one screen that shows every registered project's aggregate health
//! (Projects pane), drills into one project's sessions (Sessions pane), and
//! previews the focused session's static record fields (Activity pane
//! skeleton — live `/activity` polling is #2119) — with the lifecycle verbs
//! (launch/kill/resume/decommission/attach) one keystroke away. This is the
//! FULL 4-pane layout landing as v1 (the owner resolved #2118 to skip a
//! 2-pane MVP).
//! What: [`run`] is the `tm projects` (bare, TTY) entry point — it primes the
//! state with one poll, then hands off to [`run_loop`] with a panic-safe
//! terminal guard (mirroring `tui::coordinator::run`'s `TerminalGuard`
//! pattern, since this screen also needs to restore the terminal on an
//! unexpected panic, not just a clean return). The event loop itself follows
//! the same draw → poll-key → dispatch → maybe-repoll shape as
//! `tui::coordinator::run_loop`.
//! Test: the pure pieces (state, poll projections, key routing, pane content)
//! are unit-tested in their own modules; this terminal-bound loop is
//! exercised by launching the TUI.
use Stdout;
use ;
use ;
use ;
use crateDaemonClient;
use handle_key;
use project_ctl_poll_daemon;
use ProjectCtlState;
// #7224: the panic-safe terminal guard moved to `tui::terminal`, shared with
// the coordinator screen and the `tm ls` session TUI.
use crateTerminalGuard;
/// How long [`run_loop`] blocks waiting for a key before redrawing.
///
/// Why: matches the coordinator/dashboard screens' input cadence — short
/// enough to feel responsive, independent of the daemon refresh cadence.
const KEY_POLL: Duration = from_millis;
/// Launch the `tm projects` 4-pane TUI against `url`, polling every `interval_ms`.
///
/// Why: the bare-`tm projects`-on-a-TTY entry point (#2118); `main.rs`'s
/// bare-dispatch branch calls this after confirming stdout is a TTY.
/// What: builds a [`DaemonClient`] for `url`, primes the state with one poll
/// (so the first frame is never an empty flash), enters raw mode + the
/// alternate screen, installs a [`TerminalGuard`], then runs [`run_loop`] on
/// the `interval_ms` cadence.
/// Test: terminal glue is exercised by launching the TUI; the loop's pure
/// pieces are unit-tested in their own modules.
pub async
/// Launch the multipane dashboard, optionally pre-selecting one session.
///
/// Why (#6483): `tm tui` and `tm attach <target>` now open THIS surface by
/// default instead of the single-pane coordinator chat. `tm attach` resolves a
/// session id first and expects the TUI to open on that session, so the entry
/// point needs the same `focus_id` seam `tui::run_focused` already had.
/// What: same client/prime/terminal-guard sequence as [`run`]; after the
/// priming poll it calls [`ProjectCtlState::focus_session`], which selects the
/// owning project and session row (and is a no-op for an id the fleet does not
/// know). A missed focus sets a notice rather than failing the launch — the
/// session may simply not have been polled yet.
/// Test: the focus selection itself is unit-tested
/// (`focus_session_selects_owning_project_and_row`); this terminal glue is
/// exercised by launching the TUI.
pub async
/// [`run_focused`] against a caller-built client, so a pinned URL (#9556)
/// reaches the poll loop.
pub async
/// The live render + poll + input loop.
///
/// Why: kept separate from [`run`] so terminal setup/teardown wraps it
/// cleanly and the loop body stays focused on draw → poll-key → dispatch →
/// maybe-repoll.
/// What: each iteration draws the frame, polls the keyboard for [`KEY_POLL`]
/// and routes any key through [`handle_key`], awaiting
/// [`actions::dispatch`] for any returned [`events::PendingAction`]. Re-polls
/// the daemon immediately after a successful mutating action (via
/// [`ProjectCtlState::take_repoll`]) or once at least `interval_ms` has
/// elapsed since the last poll. Exits once `should_exit` is set (`q` /
/// Ctrl-C).
async