mirror of
https://github.com/openai/codex.git
synced 2026-05-04 05:11:37 +03:00
## Why
`bazel.yml` already builds and tests the Bazel graph, but `rust-ci.yml`
still runs `cargo clippy` separately. This PR starts the transition to a
Bazel-backed lint lane for `codex-rs` so we can eventually replace the
duplicate Rust build, test, and lint work with Bazel while explicitly
keeping the V8 Bazel path out of scope for now.
To make that lane practical, the workflow also needs to look like the
Bazel job we already trust. That means sharing the common Bazel setup
and invocation logic instead of hand-copying it, and covering the arm64
macOS path in addition to Linux.
Landing the workflow green also required fixing the first lint findings
that Bazel surfaced and adding the matching local entrypoint.
## What changed
- add a reusable `build:clippy` config to `.bazelrc` and export
`codex-rs/clippy.toml` from `codex-rs/BUILD.bazel` so Bazel can run the
repository's existing Clippy policy
- add `just bazel-clippy` so the local developer entrypoint matches the
new CI lane
- extend `.github/workflows/bazel.yml` with a dedicated Bazel clippy job
for `codex-rs`, scoped to `//codex-rs/... -//codex-rs/v8-poc:all`
- run that clippy job on Linux x64 and arm64 macOS
- factor the shared Bazel workflow setup into
`.github/actions/setup-bazel-ci/action.yml` and the shared Bazel
invocation logic into `.github/scripts/run-bazel-ci.sh` so the clippy
and build/test jobs stay aligned
- fix the first Bazel-clippy findings needed to keep the lane green,
including the cross-target `cmsghdr::cmsg_len` normalization in
`codex-rs/shell-escalation/src/unix/socket.rs` and the no-`voice-input`
dead-code warnings in `codex-rs/tui` and `codex-rs/tui_app_server`
## Verification
- `just bazel-clippy`
- `RUNNER_OS=macOS ./.github/scripts/run-bazel-ci.sh -- build
--config=clippy --build_metadata=COMMIT_SHA=local-check
--build_metadata=TAG_job=clippy -- //codex-rs/...
-//codex-rs/v8-poc:all`
- `bazel build --config=clippy
//codex-rs/shell-escalation:shell-escalation`
- `CARGO_TARGET_DIR=/tmp/codex4-shell-escalation-test cargo test -p
codex-shell-escalation`
- `ruby -e 'require "yaml";
YAML.load_file(".github/workflows/bazel.yml");
YAML.load_file(".github/actions/setup-bazel-ci/action.yml")'`
## Notes
- `CARGO_TARGET_DIR=/tmp/codex4-tui-app-server-test cargo test -p
codex-tui-app-server` still hits existing guardian-approvals test and
snapshot failures unrelated to this PR's Bazel-clippy changes.
Related: #15954
593 lines
19 KiB
Rust
593 lines
19 KiB
Rust
//! Application-level events used to coordinate UI actions.
|
|
//!
|
|
//! `AppEvent` is the internal message bus between UI components and the top-level `App` loop.
|
|
//! Widgets emit events to request actions that must be handled at the app layer (like opening
|
|
//! pickers, persisting configuration, or shutting down the agent), without needing direct access to
|
|
//! `App` internals.
|
|
//!
|
|
//! Exit is modelled explicitly via `AppEvent::Exit(ExitMode)` so callers can request shutdown-first
|
|
//! quits without reaching into the app loop or coupling to shutdown/exit sequencing.
|
|
|
|
use std::path::PathBuf;
|
|
|
|
use codex_app_server_protocol::McpServerStatus;
|
|
use codex_app_server_protocol::PluginInstallResponse;
|
|
use codex_app_server_protocol::PluginListResponse;
|
|
use codex_app_server_protocol::PluginReadParams;
|
|
use codex_app_server_protocol::PluginReadResponse;
|
|
use codex_app_server_protocol::PluginUninstallResponse;
|
|
use codex_chatgpt::connectors::AppInfo;
|
|
use codex_file_search::FileMatch;
|
|
use codex_protocol::ThreadId;
|
|
use codex_protocol::openai_models::ModelPreset;
|
|
use codex_protocol::protocol::GetHistoryEntryResponseEvent;
|
|
use codex_protocol::protocol::Op;
|
|
use codex_protocol::protocol::RateLimitSnapshot;
|
|
use codex_utils_absolute_path::AbsolutePathBuf;
|
|
use codex_utils_approval_presets::ApprovalPreset;
|
|
|
|
use crate::bottom_pane::ApprovalRequest;
|
|
use crate::bottom_pane::StatusLineItem;
|
|
use crate::bottom_pane::TerminalTitleItem;
|
|
use crate::history_cell::HistoryCell;
|
|
|
|
use codex_core::config::types::ApprovalsReviewer;
|
|
use codex_features::Feature;
|
|
use codex_protocol::config_types::CollaborationModeMask;
|
|
use codex_protocol::config_types::Personality;
|
|
use codex_protocol::config_types::ServiceTier;
|
|
use codex_protocol::openai_models::ReasoningEffort;
|
|
use codex_protocol::protocol::AskForApproval;
|
|
use codex_protocol::protocol::SandboxPolicy;
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
pub(crate) enum RealtimeAudioDeviceKind {
|
|
Microphone,
|
|
Speaker,
|
|
}
|
|
|
|
impl RealtimeAudioDeviceKind {
|
|
pub(crate) fn title(self) -> &'static str {
|
|
match self {
|
|
Self::Microphone => "Microphone",
|
|
Self::Speaker => "Speaker",
|
|
}
|
|
}
|
|
|
|
pub(crate) fn noun(self) -> &'static str {
|
|
match self {
|
|
Self::Microphone => "microphone",
|
|
Self::Speaker => "speaker",
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
#[cfg_attr(not(target_os = "windows"), allow(dead_code))]
|
|
pub(crate) enum WindowsSandboxEnableMode {
|
|
Elevated,
|
|
Legacy,
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
#[cfg_attr(not(target_os = "windows"), allow(dead_code))]
|
|
pub(crate) struct ConnectorsSnapshot {
|
|
pub(crate) connectors: Vec<AppInfo>,
|
|
}
|
|
|
|
#[allow(clippy::large_enum_variant)]
|
|
#[derive(Debug)]
|
|
pub(crate) enum AppEvent {
|
|
/// Open the agent picker for switching active threads.
|
|
OpenAgentPicker,
|
|
/// Switch the active thread to the selected agent.
|
|
SelectAgentThread(ThreadId),
|
|
|
|
/// Submit an op to the specified thread, regardless of current focus.
|
|
SubmitThreadOp {
|
|
thread_id: ThreadId,
|
|
op: Op,
|
|
},
|
|
|
|
/// Deliver a synthetic history lookup response to a specific thread channel.
|
|
ThreadHistoryEntryResponse {
|
|
thread_id: ThreadId,
|
|
event: GetHistoryEntryResponseEvent,
|
|
},
|
|
|
|
/// Start a new session.
|
|
NewSession,
|
|
|
|
/// Clear the terminal UI (screen + scrollback), start a fresh session, and keep the
|
|
/// previous chat resumable.
|
|
ClearUi,
|
|
|
|
/// Open the resume picker inside the running TUI session.
|
|
OpenResumePicker,
|
|
|
|
/// Fork the current session into a new thread.
|
|
ForkCurrentSession,
|
|
|
|
/// Request to exit the application.
|
|
///
|
|
/// Use `ShutdownFirst` for user-initiated quits so core cleanup runs and the
|
|
/// UI exits only after `ShutdownComplete`. `Immediate` is a last-resort
|
|
/// escape hatch that skips shutdown and may drop in-flight work (e.g.,
|
|
/// background tasks, rollout flush, or child process cleanup).
|
|
Exit(ExitMode),
|
|
|
|
/// Request to exit the application due to a fatal error.
|
|
#[allow(dead_code)]
|
|
FatalExitRequest(String),
|
|
|
|
/// Forward an `Op` to the Agent. Using an `AppEvent` for this avoids
|
|
/// bubbling channels through layers of widgets.
|
|
CodexOp(Op),
|
|
|
|
/// Kick off an asynchronous file search for the given query (text after
|
|
/// the `@`). Previous searches may be cancelled by the app layer so there
|
|
/// is at most one in-flight search.
|
|
StartFileSearch(String),
|
|
|
|
/// Result of a completed asynchronous file search. The `query` echoes the
|
|
/// original search term so the UI can decide whether the results are
|
|
/// still relevant.
|
|
FileSearchResult {
|
|
query: String,
|
|
matches: Vec<FileMatch>,
|
|
},
|
|
|
|
/// Result of refreshing rate limits
|
|
#[allow(dead_code)]
|
|
RateLimitSnapshotFetched(RateLimitSnapshot),
|
|
|
|
/// Result of prefetching connectors.
|
|
ConnectorsLoaded {
|
|
result: Result<ConnectorsSnapshot, String>,
|
|
is_final: bool,
|
|
},
|
|
|
|
/// Result of computing a `/diff` command.
|
|
DiffResult(String),
|
|
|
|
/// Open the app link view in the bottom pane.
|
|
OpenAppLink {
|
|
app_id: String,
|
|
title: String,
|
|
description: Option<String>,
|
|
instructions: String,
|
|
url: String,
|
|
is_installed: bool,
|
|
is_enabled: bool,
|
|
},
|
|
|
|
/// Open the provided URL in the user's browser.
|
|
OpenUrlInBrowser {
|
|
url: String,
|
|
},
|
|
|
|
/// Refresh app connector state and mention bindings.
|
|
RefreshConnectors {
|
|
force_refetch: bool,
|
|
},
|
|
|
|
/// Fetch plugin marketplace state for the provided working directory.
|
|
FetchPluginsList {
|
|
cwd: PathBuf,
|
|
},
|
|
|
|
/// Result of fetching plugin marketplace state.
|
|
PluginsLoaded {
|
|
cwd: PathBuf,
|
|
result: Result<PluginListResponse, String>,
|
|
},
|
|
|
|
/// Replace the plugins popup with a plugin-detail loading state.
|
|
OpenPluginDetailLoading {
|
|
plugin_display_name: String,
|
|
},
|
|
|
|
/// Fetch detail for a specific plugin from a marketplace.
|
|
FetchPluginDetail {
|
|
cwd: PathBuf,
|
|
params: PluginReadParams,
|
|
},
|
|
|
|
/// Result of fetching plugin detail.
|
|
PluginDetailLoaded {
|
|
cwd: PathBuf,
|
|
result: Result<PluginReadResponse, String>,
|
|
},
|
|
|
|
/// Replace the plugins popup with an install loading state.
|
|
OpenPluginInstallLoading {
|
|
plugin_display_name: String,
|
|
},
|
|
|
|
/// Replace the plugins popup with an uninstall loading state.
|
|
OpenPluginUninstallLoading {
|
|
plugin_display_name: String,
|
|
},
|
|
|
|
/// Install a specific plugin from a marketplace.
|
|
FetchPluginInstall {
|
|
cwd: PathBuf,
|
|
marketplace_path: AbsolutePathBuf,
|
|
plugin_name: String,
|
|
plugin_display_name: String,
|
|
},
|
|
|
|
/// Result of installing a plugin.
|
|
PluginInstallLoaded {
|
|
cwd: PathBuf,
|
|
marketplace_path: AbsolutePathBuf,
|
|
plugin_name: String,
|
|
plugin_display_name: String,
|
|
result: Result<PluginInstallResponse, String>,
|
|
},
|
|
|
|
/// Uninstall a specific plugin by canonical plugin id.
|
|
FetchPluginUninstall {
|
|
cwd: PathBuf,
|
|
plugin_id: String,
|
|
plugin_display_name: String,
|
|
},
|
|
|
|
/// Result of uninstalling a plugin.
|
|
PluginUninstallLoaded {
|
|
cwd: PathBuf,
|
|
plugin_id: String,
|
|
plugin_display_name: String,
|
|
result: Result<PluginUninstallResponse, String>,
|
|
},
|
|
|
|
/// Advance the post-install plugin app-auth flow.
|
|
PluginInstallAuthAdvance {
|
|
refresh_connectors: bool,
|
|
},
|
|
|
|
/// Abandon the post-install plugin app-auth flow.
|
|
PluginInstallAuthAbandon,
|
|
|
|
/// Fetch MCP inventory via app-server RPCs and render it into history.
|
|
FetchMcpInventory,
|
|
|
|
/// Result of fetching MCP inventory via app-server RPCs.
|
|
McpInventoryLoaded {
|
|
result: Result<Vec<McpServerStatus>, String>,
|
|
},
|
|
|
|
InsertHistoryCell(Box<dyn HistoryCell>),
|
|
|
|
/// Apply rollback semantics to local transcript cells.
|
|
///
|
|
/// This is emitted when rollback was not initiated by the current
|
|
/// backtrack flow so trimming occurs in AppEvent queue order relative to
|
|
/// inserted history cells.
|
|
ApplyThreadRollback {
|
|
num_turns: u32,
|
|
},
|
|
|
|
StartCommitAnimation,
|
|
StopCommitAnimation,
|
|
CommitTick,
|
|
|
|
/// Update the current reasoning effort in the running app and widget.
|
|
UpdateReasoningEffort(Option<ReasoningEffort>),
|
|
|
|
/// Update the current model slug in the running app and widget.
|
|
UpdateModel(String),
|
|
|
|
/// Update the active collaboration mask in the running app and widget.
|
|
UpdateCollaborationMode(CollaborationModeMask),
|
|
|
|
/// Update the current personality in the running app and widget.
|
|
UpdatePersonality(Personality),
|
|
|
|
/// Persist the selected model and reasoning effort to the appropriate config.
|
|
PersistModelSelection {
|
|
model: String,
|
|
effort: Option<ReasoningEffort>,
|
|
},
|
|
|
|
/// Persist the selected personality to the appropriate config.
|
|
PersistPersonalitySelection {
|
|
personality: Personality,
|
|
},
|
|
|
|
/// Persist the selected service tier to the appropriate config.
|
|
PersistServiceTierSelection {
|
|
service_tier: Option<ServiceTier>,
|
|
},
|
|
|
|
/// Open the device picker for a realtime microphone or speaker.
|
|
OpenRealtimeAudioDeviceSelection {
|
|
kind: RealtimeAudioDeviceKind,
|
|
},
|
|
|
|
/// Persist the selected realtime microphone or speaker to top-level config.
|
|
#[cfg_attr(
|
|
any(target_os = "linux", not(feature = "voice-input")),
|
|
allow(dead_code)
|
|
)]
|
|
PersistRealtimeAudioDeviceSelection {
|
|
kind: RealtimeAudioDeviceKind,
|
|
name: Option<String>,
|
|
},
|
|
|
|
/// Restart the selected realtime microphone or speaker locally.
|
|
RestartRealtimeAudioDevice {
|
|
kind: RealtimeAudioDeviceKind,
|
|
},
|
|
|
|
/// Open the reasoning selection popup after picking a model.
|
|
OpenReasoningPopup {
|
|
model: ModelPreset,
|
|
},
|
|
|
|
/// Open the Plan-mode reasoning scope prompt for the selected model/effort.
|
|
OpenPlanReasoningScopePrompt {
|
|
model: String,
|
|
effort: Option<ReasoningEffort>,
|
|
},
|
|
|
|
/// Open the full model picker (non-auto models).
|
|
OpenAllModelsPopup {
|
|
models: Vec<ModelPreset>,
|
|
},
|
|
|
|
/// Open the confirmation prompt before enabling full access mode.
|
|
OpenFullAccessConfirmation {
|
|
preset: ApprovalPreset,
|
|
return_to_permissions: bool,
|
|
},
|
|
|
|
/// Open the Windows world-writable directories warning.
|
|
/// If `preset` is `Some`, the confirmation will apply the provided
|
|
/// approval/sandbox configuration on Continue; if `None`, it performs no
|
|
/// policy change and only acknowledges/dismisses the warning.
|
|
#[cfg_attr(not(target_os = "windows"), allow(dead_code))]
|
|
OpenWorldWritableWarningConfirmation {
|
|
preset: Option<ApprovalPreset>,
|
|
/// Up to 3 sample world-writable directories to display in the warning.
|
|
sample_paths: Vec<String>,
|
|
/// If there are more than `sample_paths`, this carries the remaining count.
|
|
extra_count: usize,
|
|
/// True when the scan failed (e.g. ACL query error) and protections could not be verified.
|
|
failed_scan: bool,
|
|
},
|
|
|
|
/// Prompt to enable the Windows sandbox feature before using Agent mode.
|
|
#[cfg_attr(not(target_os = "windows"), allow(dead_code))]
|
|
OpenWindowsSandboxEnablePrompt {
|
|
preset: ApprovalPreset,
|
|
},
|
|
|
|
/// Open the Windows sandbox fallback prompt after declining or failing elevation.
|
|
#[cfg_attr(not(target_os = "windows"), allow(dead_code))]
|
|
OpenWindowsSandboxFallbackPrompt {
|
|
preset: ApprovalPreset,
|
|
},
|
|
|
|
/// Begin the elevated Windows sandbox setup flow.
|
|
#[cfg_attr(not(target_os = "windows"), allow(dead_code))]
|
|
BeginWindowsSandboxElevatedSetup {
|
|
preset: ApprovalPreset,
|
|
},
|
|
|
|
/// Begin the non-elevated Windows sandbox setup flow.
|
|
#[cfg_attr(not(target_os = "windows"), allow(dead_code))]
|
|
BeginWindowsSandboxLegacySetup {
|
|
preset: ApprovalPreset,
|
|
},
|
|
|
|
/// Begin a non-elevated grant of read access for an additional directory.
|
|
#[cfg_attr(not(target_os = "windows"), allow(dead_code))]
|
|
BeginWindowsSandboxGrantReadRoot {
|
|
path: String,
|
|
},
|
|
|
|
/// Result of attempting to grant read access for an additional directory.
|
|
#[cfg_attr(not(target_os = "windows"), allow(dead_code))]
|
|
WindowsSandboxGrantReadRootCompleted {
|
|
path: PathBuf,
|
|
error: Option<String>,
|
|
},
|
|
|
|
/// Enable the Windows sandbox feature and switch to Agent mode.
|
|
#[cfg_attr(not(target_os = "windows"), allow(dead_code))]
|
|
EnableWindowsSandboxForAgentMode {
|
|
preset: ApprovalPreset,
|
|
mode: WindowsSandboxEnableMode,
|
|
},
|
|
|
|
/// Update the Windows sandbox feature mode without changing approval presets.
|
|
#[cfg_attr(not(target_os = "windows"), allow(dead_code))]
|
|
|
|
/// Update the current approval policy in the running app and widget.
|
|
UpdateAskForApprovalPolicy(AskForApproval),
|
|
|
|
/// Update the current sandbox policy in the running app and widget.
|
|
UpdateSandboxPolicy(SandboxPolicy),
|
|
|
|
/// Update the current approvals reviewer in the running app and widget.
|
|
UpdateApprovalsReviewer(ApprovalsReviewer),
|
|
|
|
/// Update feature flags and persist them to the top-level config.
|
|
UpdateFeatureFlags {
|
|
updates: Vec<(Feature, bool)>,
|
|
},
|
|
|
|
/// Update whether the full access warning prompt has been acknowledged.
|
|
UpdateFullAccessWarningAcknowledged(bool),
|
|
|
|
/// Update whether the world-writable directories warning has been acknowledged.
|
|
#[cfg_attr(not(target_os = "windows"), allow(dead_code))]
|
|
UpdateWorldWritableWarningAcknowledged(bool),
|
|
|
|
/// Update whether the rate limit switch prompt has been acknowledged for the session.
|
|
UpdateRateLimitSwitchPromptHidden(bool),
|
|
|
|
/// Update the Plan-mode-specific reasoning effort in memory.
|
|
UpdatePlanModeReasoningEffort(Option<ReasoningEffort>),
|
|
|
|
/// Persist the acknowledgement flag for the full access warning prompt.
|
|
PersistFullAccessWarningAcknowledged,
|
|
|
|
/// Persist the acknowledgement flag for the world-writable directories warning.
|
|
#[cfg_attr(not(target_os = "windows"), allow(dead_code))]
|
|
PersistWorldWritableWarningAcknowledged,
|
|
|
|
/// Persist the acknowledgement flag for the rate limit switch prompt.
|
|
PersistRateLimitSwitchPromptHidden,
|
|
|
|
/// Persist the Plan-mode-specific reasoning effort.
|
|
PersistPlanModeReasoningEffort(Option<ReasoningEffort>),
|
|
|
|
/// Persist the acknowledgement flag for the model migration prompt.
|
|
PersistModelMigrationPromptAcknowledged {
|
|
from_model: String,
|
|
to_model: String,
|
|
},
|
|
|
|
/// Skip the next world-writable scan (one-shot) after a user-confirmed continue.
|
|
#[cfg_attr(not(target_os = "windows"), allow(dead_code))]
|
|
SkipNextWorldWritableScan,
|
|
|
|
/// Re-open the approval presets popup.
|
|
OpenApprovalsPopup,
|
|
|
|
/// Open the skills list popup.
|
|
OpenSkillsList,
|
|
|
|
/// Open the skills enable/disable picker.
|
|
OpenManageSkillsPopup,
|
|
|
|
/// Enable or disable a skill by path.
|
|
SetSkillEnabled {
|
|
path: PathBuf,
|
|
enabled: bool,
|
|
},
|
|
|
|
/// Enable or disable an app by connector ID.
|
|
SetAppEnabled {
|
|
id: String,
|
|
enabled: bool,
|
|
},
|
|
|
|
/// Notify that the manage skills popup was closed.
|
|
ManageSkillsClosed,
|
|
|
|
/// Re-open the permissions presets popup.
|
|
OpenPermissionsPopup,
|
|
|
|
/// Live update for the in-progress voice recording placeholder. Carries
|
|
/// the placeholder `id` and the text to display (e.g., an ASCII meter).
|
|
#[cfg(not(target_os = "linux"))]
|
|
UpdateRecordingMeter {
|
|
id: String,
|
|
text: String,
|
|
},
|
|
|
|
/// Voice transcription finished for the given placeholder id.
|
|
#[cfg(not(target_os = "linux"))]
|
|
#[cfg_attr(not(feature = "voice-input"), allow(dead_code))]
|
|
TranscriptionComplete {
|
|
id: String,
|
|
text: String,
|
|
},
|
|
|
|
/// Voice transcription failed; remove the placeholder identified by `id`.
|
|
#[cfg(not(target_os = "linux"))]
|
|
TranscriptionFailed {
|
|
id: String,
|
|
#[allow(dead_code)]
|
|
error: String,
|
|
},
|
|
|
|
/// Open the branch picker option from the review popup.
|
|
OpenReviewBranchPicker(PathBuf),
|
|
|
|
/// Open the commit picker option from the review popup.
|
|
OpenReviewCommitPicker(PathBuf),
|
|
|
|
/// Open the custom prompt option from the review popup.
|
|
OpenReviewCustomPrompt,
|
|
|
|
/// Submit a user message with an explicit collaboration mask.
|
|
SubmitUserMessageWithMode {
|
|
text: String,
|
|
collaboration_mode: CollaborationModeMask,
|
|
},
|
|
|
|
/// Open the approval popup.
|
|
FullScreenApprovalRequest(ApprovalRequest),
|
|
|
|
/// Open the feedback note entry overlay after the user selects a category.
|
|
OpenFeedbackNote {
|
|
category: FeedbackCategory,
|
|
include_logs: bool,
|
|
},
|
|
|
|
/// Open the upload consent popup for feedback after selecting a category.
|
|
OpenFeedbackConsent {
|
|
category: FeedbackCategory,
|
|
},
|
|
|
|
/// Launch the external editor after a normal draw has completed.
|
|
LaunchExternalEditor,
|
|
|
|
/// Async update of the current git branch for status line rendering.
|
|
StatusLineBranchUpdated {
|
|
cwd: PathBuf,
|
|
branch: Option<String>,
|
|
},
|
|
/// Apply a user-confirmed status-line item ordering/selection.
|
|
StatusLineSetup {
|
|
items: Vec<StatusLineItem>,
|
|
},
|
|
/// Dismiss the status-line setup UI without changing config.
|
|
StatusLineSetupCancelled,
|
|
|
|
/// Apply a user-confirmed terminal-title item ordering/selection.
|
|
TerminalTitleSetup {
|
|
items: Vec<TerminalTitleItem>,
|
|
},
|
|
/// Apply a temporary terminal-title preview while the setup UI is open.
|
|
TerminalTitleSetupPreview {
|
|
items: Vec<TerminalTitleItem>,
|
|
},
|
|
/// Dismiss the terminal-title setup UI without changing config.
|
|
TerminalTitleSetupCancelled,
|
|
|
|
/// Apply a user-confirmed syntax theme selection.
|
|
SyntaxThemeSelected {
|
|
name: String,
|
|
},
|
|
}
|
|
|
|
/// The exit strategy requested by the UI layer.
|
|
///
|
|
/// Most user-initiated exits should use `ShutdownFirst` so core cleanup runs and the UI exits only
|
|
/// after core acknowledges completion. `Immediate` is an escape hatch for cases where shutdown has
|
|
/// already completed (or is being bypassed) and the UI loop should terminate right away.
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
pub(crate) enum ExitMode {
|
|
/// Shutdown core and exit after completion.
|
|
ShutdownFirst,
|
|
/// Exit the UI loop immediately without waiting for shutdown.
|
|
///
|
|
/// This skips `Op::Shutdown`, so any in-flight work may be dropped and
|
|
/// cleanup that normally runs before `ShutdownComplete` can be missed.
|
|
Immediate,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
pub(crate) enum FeedbackCategory {
|
|
BadResult,
|
|
GoodResult,
|
|
Bug,
|
|
SafetyCheck,
|
|
Other,
|
|
}
|