pub struct App {Show 42 fields
pub stage: Stage,
pub previous_stage: Stage,
pub focused_pane: Pane,
pub team: TeamSnapshot,
pub selected_agent: Option<usize>,
pub detail_buffer: Vec<String>,
pub version: &'static str,
pub capabilities: Capabilities,
pub splash_started: Instant,
pub last_refresh: Instant,
pub last_pane_refresh: Instant,
pub running: bool,
pub tutorial_completed: bool,
pub mailbox_tab: MailboxTab,
pub mailbox: MailboxBuffers,
pub mailbox_input_mode: Option<MailboxInputKind>,
pub mailbox_input_snapshot: String,
pub mailbox_detail_modal: Option<MessageRow>,
pub mailbox_detail_scroll: u16,
pub now_secs: f64,
pub pending_approvals: Vec<Approval>,
pub selected_approval: usize,
pub approval_error: Option<String>,
pub compose_target: Option<ComposeTarget>,
pub compose_editor: Editor,
pub compose_error: Option<String>,
pub layout: MainLayout,
pub wall_scroll: usize,
pub selected_channel: Option<usize>,
pub detail_splits: Vec<(String, SplitOrientation)>,
pub selected_split: usize,
pub pending_chord: Option<KeyCode>,
pub tutorial_pending_for_team: bool,
pub spinner_frame: usize,
pub tutorial_step: usize,
pub compose_picker_open: bool,
pub compose_picker_index: usize,
pub compose_attach_input_open: bool,
pub compose_attach_buffer: String,
pub last_synced_pane_sizes: HashMap<String, (u16, u16)>,
pub sysinfo: System,
pub rate_limit_indicator_enabled: bool,
}Fields§
§stage: Stage§previous_stage: StageTracked so QuitConfirm can return to whichever stage opened it.
focused_pane: Pane§team: TeamSnapshot§selected_agent: Option<usize>Index into team.agents of the agent the detail pane is
streaming. None when the team is empty or roster
navigation hasn’t picked one yet.
detail_buffer: Vec<String>Lines from the most recent pane capture. Bounded to the last
MAX_DETAIL_LINES so the buffer doesn’t grow unboundedly
over a long-running session.
version: &'static str§capabilities: Capabilities§splash_started: Instant§last_refresh: InstantLast time the snapshot + pane capture were refreshed. Used by
tick() to gate the next refresh.
last_pane_refresh: InstantLast time the focused agent’s pane was re-captured on the fast
PANE_REFRESH_INTERVAL cadence (between full refreshes).
running: bool§tutorial_completed: boolFirst-launch detection — when the marker file exists, future stacked-PRs (PR-UI-7) skip the tutorial after splash. PR-UI-1 only reads the flag; nothing routes off it yet.
mailbox_tab: MailboxTabActive tab inside the mailbox pane (PR-UI-3). Walked with
← / → when focused_pane == Mailbox (T-124 hard-swapped
the prior [ / ] chord for arrow keys; T-074 bug 6 is
the gating-on-focus invariant). Tab always cycles pane
focus, never mailbox tabs — the previous “Tab cycles tabs
when mailbox is focused” shape stranded operators inside
the mailbox.
mailbox: MailboxBuffersPer-tab buffers + cursors for the focused agent’s mailbox view. Reset whenever the focused agent changes — switching agents starts the operator at the head of fresh traffic.
mailbox_input_mode: Option<MailboxInputKind>T-131 PR-2: which mailbox input the operator is currently
editing, if any. Singleton — only one input open at a time
across all tabs. When Some, Pane::Mailbox keystrokes route
to the per-tab filter_text / search_text buffer on
MailboxBuffers (the data lives there, per-tab; this is
just the editing-UI flag).
mailbox_input_snapshot: StringPre-open snapshot of the active input buffer — restored on
Esc (cancel-revert) so the operator can back out without
losing the prior filter/search. Empty between sessions.
mailbox_detail_modal: Option<MessageRow>T-131 PR-3: mailbox detail modal — the row content the
operator opened. Captured AT open-time and rendered from
here independent of the underlying mailbox buffer: any
subsequent extend() drain that would shift indices on the
row cursor leaves this snapshot intact, so the operator
sees the message they clicked, not whatever now happens to
sit at the same index (variant (a) locked). None when no
modal is open.
mailbox_detail_scroll: u16T-131 PR-3: vertical scroll offset (in wrapped body lines)
within an open detail modal. Reset to 0 when the modal
opens; bumped by j / Down / k / Up while the modal
is the active stage. Ignored when no modal is open.
now_secs: f64T-131 PR-4: wall-clock seconds at the last render tick. The
mailbox-row relative-time indicator (2m / 1h / 3d)
reads from here so render is a pure function of App —
snapshot tests can pin time deterministically by setting
this field (otherwise wall-clock would diff snapshots every
run). The run loop refreshes this before each
terminal.draw; defaults to 0 in App::new so a freshly
constructed test app + sent_at=0 fixture rows render now
stably.
pending_approvals: Vec<Approval>Pending approvals snapshot (PR-UI-4). Drives the conditional
stripe at the top of Triptych and the modal opened by a.
selected_approval: usizeIndex into pending_approvals of the row the modal is
currently showing. Reset to 0 each time the modal opens;
j / k (or ↑ / ↓) cycle.
approval_error: Option<String>Last error from a CLI-routed Approve/Deny call — surfaced inline in the modal so the operator sees why a decision didn’t take.
compose_target: Option<ComposeTarget>Open compose target — Some while Stage::ComposeModal
is the active stage, None otherwise. Stored on App so
the editor’s contents survive rerenders.
compose_editor: EditorEditor backing the compose modal. Reset to default() each
time the modal opens so an old draft from a prior
invocation can’t leak into a new send.
compose_error: Option<String>Last error from a CLI-routed send call — surfaced inline in the modal so the operator sees rate-limit / ACL-block errors without leaving the UI.
layout: MainLayoutActive main-view layout (PR-UI-6). Triptych is the default;
Ctrl+W toggles Wall, Ctrl+M toggles MailboxFirst.
wall_scroll: usizeTop-of-window agent index for the Wall view’s vertical scroll. SPEC §3 caps visible tiles at 4; this offsets which 4-agent window is shown when the team has more.
selected_channel: Option<usize>Selected channel index (into team.channels) for the
MailboxFirst layout’s channel list and for the broadcast
picker. None until the operator picks one.
detail_splits: Vec<(String, SplitOrientation)>Splits within Triptych’s detail pane (PR-UI-6). When
non-empty, the detail pane subdivides; each entry pairs an
agent id with the per-split orientation (PR-UI-7 lift of
the Q1 deferral). selected_split is the vim-window-motion
focus.
selected_split: usize§pending_chord: Option<KeyCode>Chord-prefix machine for Ctrl+W follow-ups (PR-UI-7 lift
of PR-UI-6’s Ctrl+Q alias). When Some(KeyCode::Char('w')),
the next key is interpreted as a Ctrl+W follow: q =
close split, o = close others. Cleared on any unrelated
keypress so a typo doesn’t leave the editor stuck.
tutorial_pending_for_team: booltrue when the operator’s first launch on this team has
not yet completed the tutorial — drives the auto-open after
splash. Reset to false on tutorial completion.
spinner_frame: usizeBrand-spinner frame counter (PR-UI-7). Bumped each refresh tick so the statusline indicator shows the app is alive.
tutorial_step: usizeTutorial step cursor (PR-UI-7). Index into
onboarding::STEPS; reset to 0 when the tutorial reopens.
compose_picker_open: boolModal substage for the broadcast channel picker (PR-UI-6).
When true the compose modal renders a picker over the
editor; selecting a channel populates compose_target and
drops back to the editor.
compose_picker_index: usizePicker selection cursor — index into team.channels.
compose_attach_input_open: boolT-32: when true, the compose modal renders a single-line
path-input overlay instead of the editor; Enter appends a
📎 attachment: <path> line to the editor body and closes the
overlay. Tab inside the editor opens it; Esc inside the
overlay cancels back to the editor (matches the picker
overlay’s modal-vs-modal symmetry from PR-UI-6).
compose_attach_buffer: StringSingle-line buffer for the path-input overlay. Reset on close so a cancelled draft can’t leak into the next attach attempt.
last_synced_pane_sizes: HashMap<String, (u16, u16)>T-199: per-session cache of the last Detail-pane size we
pushed to tmux resize-pane. The run loop diffs the current
Detail rect against this on every frame and only spawns the
tmux command when the size actually changed — common case
(no resize, no focus switch) is a HashMap lookup. Keyed by
tmux_session (e.g. t-hello-manager). See
crate::pane_resize.
sysinfo: SystemT-209: live system handle for the bottom status bar’s
CPU% + RAM% indicator. Refreshed in-place on the existing
1-second App tick (see refresh_with_default_sources and the
run-loop tick at the top of run()); no background thread.
default-features = false + only the system feature is
enabled in the dep to keep the compile surface narrow. See
crate::status_bar.
rate_limit_indicator_enabled: boolT-212 preview gate. true when TEAMCTL_UI_RATE_LIMIT_INDICATOR
was set at App::new(), false otherwise. The bottom status
bar’s center slot only renders when this is true — opt-in
while the indicator’s data shape (currently reset-time only)
stabilizes against the eventual usage-% data path. Tests can
flip the field directly to exercise both branches without
process-wide env-var racing.
Implementations§
Source§impl App
impl App
Sourcepub fn new() -> Self
pub fn new() -> Self
Construct an empty App — no team snapshot loaded. Used by
tests and as the splash-stage default. Production launch
goes through App::launch() which immediately runs an
initial refresh() so the splash screen already shows the
real team name + agent count.
Sourcepub fn enter_help_overlay(&mut self)
pub fn enter_help_overlay(&mut self)
Per-tutorial-step cursor (used by Stage::Tutorial). Wraps
at the end so t-then-keys walks the full tour.
pub fn close_help_overlay(&mut self)
pub fn enter_tutorial(&mut self)
pub fn close_tutorial(&mut self)
pub fn tutorial_advance(&mut self)
pub fn tutorial_back(&mut self)
pub fn toggle_wall_layout(&mut self)
pub fn toggle_mailbox_first_layout(&mut self)
pub fn wall_scroll_up(&mut self)
pub fn wall_scroll_down(&mut self)
pub fn select_next_channel(&mut self)
pub fn select_prev_channel(&mut self)
Sourcepub fn add_detail_split_vertical(&mut self)
pub fn add_detail_split_vertical(&mut self)
Add a split for the focused agent (or current selection)
to the detail pane. Cap at 4 splits per the SPEC §3 cap.
Add a vertical split (PR-UI-7). Ctrl+| calls this.
Sourcepub fn add_detail_split_horizontal(&mut self)
pub fn add_detail_split_horizontal(&mut self)
Add a horizontal split (PR-UI-7). Ctrl+- calls this.
Sourcepub fn add_detail_split(&mut self)
pub fn add_detail_split(&mut self)
Back-compat shim — earlier PRs called the unsuffixed name.
Defaults to vertical (matching the most-common chord
Ctrl+|). Kept so the test surface PR-UI-6 pinned doesn’t
drift.
pub fn close_focused_split(&mut self)
pub fn cycle_split_next(&mut self)
pub fn cycle_split_prev(&mut self)
Sourcepub fn enter_compose_broadcast_with_picker(&mut self)
pub fn enter_compose_broadcast_with_picker(&mut self)
Open the broadcast compose flow — picker first when at
least one channel is declared, else fall back to the
project’s all channel (PR-UI-5 behaviour) on the
assumption that all always exists in production composes.
pub fn picker_next(&mut self)
pub fn picker_prev(&mut self)
pub fn picker_confirm(&mut self)
Sourcepub fn open_compose_attach_input(&mut self)
pub fn open_compose_attach_input(&mut self)
T-32: open the path-input overlay. Resets the buffer so a previously-cancelled draft can’t carry over.
Sourcepub fn confirm_compose_attach_input(&mut self)
pub fn confirm_compose_attach_input(&mut self)
T-32: append a 📎 attachment: <path> line to the compose
editor and close the overlay. The line lands as a fresh row
at the end of the body so the operator can edit it (or delete
it) before sending. Whitespace-only buffers are ignored — Tab
followed by Enter shouldn’t insert an empty marker.
pub fn close_compose_attach_input(&mut self)
pub fn cycle_mailbox_tab(&mut self)
pub fn cycle_mailbox_tab_back(&mut self)
pub fn mailbox_cursor_down(&mut self)
pub fn mailbox_cursor_up(&mut self)
pub fn mailbox_page_down(&mut self)
pub fn mailbox_page_up(&mut self)
pub fn mailbox_cursor_home(&mut self)
pub fn mailbox_cursor_end(&mut self)
Sourcepub fn open_mailbox_filter_input(&mut self)
pub fn open_mailbox_filter_input(&mut self)
Open the sender-substring filter input on the active tab. Snapshots the current value so Esc can revert.
Sourcepub fn open_mailbox_search_input(&mut self)
pub fn open_mailbox_search_input(&mut self)
Open the body-substring search input on the active tab. Snapshots the current value so Esc can revert.
Sourcepub fn mailbox_input_push_char(&mut self, c: char)
pub fn mailbox_input_push_char(&mut self, c: char)
Append c to the active input buffer. visible_indices
recomputes live; the cursor re-clamps inside MailboxBuffers.
Sourcepub fn mailbox_input_pop_char(&mut self)
pub fn mailbox_input_pop_char(&mut self)
Pop one character from the active input buffer.
Sourcepub fn mailbox_input_confirm(&mut self)
pub fn mailbox_input_confirm(&mut self)
Confirm and close the input — keep the operator’s typed text.
Sourcepub fn mailbox_input_cancel(&mut self)
pub fn mailbox_input_cancel(&mut self)
Cancel and close the input — revert the active buffer to the pre-open snapshot so the operator can back out without losing the prior filter / search.
Sourcepub fn open_mailbox_detail_modal(&mut self)
pub fn open_mailbox_detail_modal(&mut self)
Open the detail modal on the currently-selected mailbox row.
No-op when visible_indices is empty (no row to select) so
Enter on an empty / fully-filtered tab silently does
nothing rather than opening a modal on garbage.
Sourcepub fn close_mailbox_detail_modal(&mut self)
pub fn close_mailbox_detail_modal(&mut self)
Close the detail modal and return to the Triptych. Clears the snapshot; the row cursor underneath is untouched.
Sourcepub fn mailbox_detail_scroll_down(&mut self)
pub fn mailbox_detail_scroll_down(&mut self)
Scroll the detail modal body one wrapped line down. Caller supplies the maximum scroll value (lines beyond which there is no content); we clamp.
Sourcepub fn mailbox_detail_scroll_up(&mut self)
pub fn mailbox_detail_scroll_up(&mut self)
Scroll the detail modal body one wrapped line up.
pub fn cycle_focus_back(&mut self)
pub fn has_pending_approvals(&self) -> bool
pub fn enter_approvals_modal(&mut self)
pub fn close_approvals_modal(&mut self)
pub fn cycle_approval_next(&mut self)
pub fn cycle_approval_prev(&mut self)
pub fn focused_approval(&self) -> Option<&Approval>
Sourcepub fn replace_approvals(&mut self, approvals: Vec<Approval>)
pub fn replace_approvals(&mut self, approvals: Vec<Approval>)
Replace the pending-approvals list. Closes the modal when
the queue empties (no row to act on); preserves the modal
otherwise but clamps selected_approval into range so an
approval resolved out-of-band doesn’t leave us pointing at
a stale index.
Sourcepub fn apply_decision<D: ApprovalDecider>(
&mut self,
decider: &D,
kind: Decision,
note: &str,
)
pub fn apply_decision<D: ApprovalDecider>( &mut self, decider: &D, kind: Decision, note: &str, )
Apply a decision to the focused approval via the injected
decider. The decider routes through teamctl approve|deny
in production; tests inject a recorder. On success the row
gets removed from the local pending_approvals snapshot
optimistically — the next refresh_approvals will reconcile
against the broker.
Sourcepub fn enter_compose_dm_for_focused(&mut self)
pub fn enter_compose_dm_for_focused(&mut self)
Open the compose modal for the focused agent (if any). The
@ chord. No-op when no agent is focused.
Sourcepub fn enter_compose_broadcast(&mut self)
pub fn enter_compose_broadcast(&mut self)
Open the compose modal targeting the project’s all
channel — the broadcast wire. The ! chord. PR-UI-5 ships
with channel scoping limited to all (the Wire tab is the
only channel context the mailbox pane currently surfaces);
PR-UI-6’s mailbox UI work will widen the scope to per-channel
targets when individual channels become first-class in the
pane.
pub fn close_compose_modal(&mut self)
Sourcepub fn apply_send<S: MessageSender, M: MailboxSource>(
&mut self,
sender: &S,
mailbox_source: &M,
)
pub fn apply_send<S: MessageSender, M: MailboxSource>( &mut self, sender: &S, mailbox_source: &M, )
Send the current compose body via the injected message
sender. Routes through teamctl send|broadcast in
production; tests inject a recorder. Closes the modal +
triggers a mailbox refresh on success; surfaces error
inline on failure.
pub fn dismiss_splash(&mut self)
pub fn cycle_focus(&mut self)
Sourcepub fn select_prev(&mut self)
pub fn select_prev(&mut self)
Move roster selection up by one — wraps at the top. No-op
when the team is empty. Does not change focused_pane.
Resets mailbox buffers when the resulting agent id differs
from the prior selection — switching agents should start the
operator at the head of fresh traffic.
Sourcepub fn select_next(&mut self)
pub fn select_next(&mut self)
Move roster selection down by one — wraps at the bottom. No-op when the team is empty.
Sourcepub fn selected_agent_id(&self) -> Option<String>
pub fn selected_agent_id(&self) -> Option<String>
<project>:<agent> of the currently selected agent, if any.
pub fn enter_quit_confirm(&mut self)
pub fn cancel_quit(&mut self)
pub fn confirm_quit(&mut self)
Sourcepub fn replace_team(&mut self, team: TeamSnapshot)
pub fn replace_team(&mut self, team: TeamSnapshot)
Replace the team snapshot. Preserves the current selection
when the agent at that index still exists; otherwise resets
to the first agent (or None for an empty team). Resets the
mailbox buffers when the resulting agent id differs from the
prior selection — same agent-changed contract as
select_next / select_prev.
Sourcepub fn focused_session(&self) -> Option<&str>
pub fn focused_session(&self) -> Option<&str>
Return the focused agent’s tmux session name, if any. Used by the run loop to know which session to capture.
Sourcepub fn stream_target_session(&self) -> Option<String>
pub fn stream_target_session(&self) -> Option<String>
Tmux session that stream-keys mode should target. Cell 0 of
the detail-pane split layout is always the focused agent;
cells 1..N are the entries in detail_splits. When the
operator has focused a non-zero split, route stream-keys to
that split’s agent — that’s the cell visually showing as the
focus ring, so it’s the one the operator expects to type into.
Sourcepub fn enter_stream_keys(&mut self)
pub fn enter_stream_keys(&mut self)
Enter stream-keys mode. No-op unless an agent is selected —
without a target session there’s nothing to forward to.
Caller is responsible for the focused-pane gate (entry chord
only fires from focused_pane == Pane::Detail).
Sourcepub fn exit_stream_keys(&mut self)
pub fn exit_stream_keys(&mut self)
Exit stream-keys mode and return to whichever stage opened it.
Esc is the only exit chord per the issue’s recommendation —
every other key (including Ctrl+C) forwards to the agent.
Sourcepub fn set_detail_buffer(&mut self, lines: Vec<String>)
pub fn set_detail_buffer(&mut self, lines: Vec<String>)
Replace the detail buffer, clipped at the recent-line cap.
Trait Implementations§
Auto Trait Implementations§
impl Freeze for App
impl RefUnwindSafe for App
impl Send for App
impl Sync for App
impl Unpin for App
impl UnsafeUnpin for App
impl UnwindSafe for App
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self>
fn instrument(self, span: Span) -> Instrumented<Self>
Source§fn in_current_span(self) -> Instrumented<Self>
fn in_current_span(self) -> Instrumented<Self>
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more