Skip to content

Commit b3cec84

Browse files
committed
feat(tui): Gate login, feedback, and backend with flags
Add conditional compilation throughout TUI crate to enable modular builds: - Define feature flags in Cargo.toml: full, login, feedback, backend-client, upstream-updates - Make codex-login, codex-feedback, codex-backend-client optional dependencies - Gate onboarding auth flow with #[cfg(feature = "login")] - Gate feedback popups and events with #[cfg(feature = "feedback")] - Gate BackendClient usage with #[cfg(feature = "backend-client")] - Hide /logout and /feedback slash commands when features disabled - Add nori/feedback.rs to redirect feedback to GitHub Discussions - Add nori/update_action.rs and nori/updates.rs for Nori update system - Gate feedback-related tests with #[cfg(feature = "feedback")] - Create FEATURE_PLAN.md documenting the implementation plan This enables the Nori CLI fork to default to ACP-only mode without OpenAI-specific functionality, while allowing full-featured builds via --features full.
1 parent 4cf82d9 commit b3cec84

16 files changed

Lines changed: 1171 additions & 10 deletions

File tree

FEATURE_PLAN.md

Lines changed: 720 additions & 0 deletions
Large diffs are not rendered by default.

codex-rs/tui/Cargo.toml

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,28 @@ name = "codex_tui"
1313
path = "src/lib.rs"
1414

1515
[features]
16+
default = []
17+
18+
# Full feature set - enables all upstream functionality
19+
full = ["login", "feedback", "backend-client", "upstream-updates"]
20+
1621
# Enable vt100-based tests (emulator) when running with `--features vt100-tests`.
1722
vt100-tests = []
1823
# Gate verbose debug logging inside the TUI implementation.
1924
debug-logs = []
2025

26+
# ChatGPT/API login functionality
27+
login = ["dep:codex-login"]
28+
29+
# Feedback to Sentry
30+
feedback = ["dep:codex-feedback"]
31+
32+
# Backend client for cloud tasks
33+
backend-client = ["dep:codex-backend-client"]
34+
35+
# Upstream (OpenAI) update checking
36+
upstream-updates = []
37+
2138
[lints]
2239
workspace = true
2340

@@ -31,16 +48,16 @@ codex-acp = { workspace = true }
3148
codex-ansi-escape = { workspace = true }
3249
codex-app-server-protocol = { workspace = true }
3350
codex-arg0 = { workspace = true }
34-
codex-backend-client = { workspace = true }
51+
codex-backend-client = { workspace = true, optional = true }
3552
codex-common = { workspace = true, features = [
3653
"cli",
3754
"elapsed",
3855
"sandbox_summary",
3956
] }
4057
codex-core = { workspace = true }
41-
codex-feedback = { workspace = true }
58+
codex-feedback = { workspace = true, optional = true }
4259
codex-file-search = { workspace = true }
43-
codex-login = { workspace = true }
60+
codex-login = { workspace = true, optional = true }
4461
codex-protocol = { workspace = true }
4562
color-eyre = { workspace = true }
4663
crossterm = { workspace = true, features = ["bracketed-paste", "event-stream"] }

codex-rs/tui/src/app.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,7 @@ pub(crate) struct App {
219219

220220
// Esc-backtracking state grouped
221221
pub(crate) backtrack: crate::app_backtrack::BacktrackState,
222+
#[cfg(feature = "feedback")]
222223
pub(crate) feedback: codex_feedback::CodexFeedback,
223224
/// Set when the user confirms an update; propagated on exit.
224225
pub(crate) pending_update_action: Option<UpdateAction>,
@@ -253,7 +254,7 @@ impl App {
253254
initial_prompt: Option<String>,
254255
initial_images: Vec<PathBuf>,
255256
resume_selection: ResumeSelection,
256-
feedback: codex_feedback::CodexFeedback,
257+
#[cfg(feature = "feedback")] feedback: codex_feedback::CodexFeedback,
257258
) -> Result<AppExitInfo> {
258259
use tokio_stream::StreamExt;
259260

@@ -298,6 +299,7 @@ impl App {
298299
initial_images: initial_images.clone(),
299300
enhanced_keys_supported,
300301
auth_manager: auth_manager.clone(),
302+
#[cfg(feature = "feedback")]
301303
feedback: feedback.clone(),
302304
expected_model: None, // No filtering for fresh sessions
303305
};
@@ -322,6 +324,7 @@ impl App {
322324
initial_images: initial_images.clone(),
323325
enhanced_keys_supported,
324326
auth_manager: auth_manager.clone(),
327+
#[cfg(feature = "feedback")]
325328
feedback: feedback.clone(),
326329
expected_model: None, // No filtering for resumed sessions
327330
};
@@ -354,6 +357,7 @@ impl App {
354357
has_emitted_history_lines: false,
355358
commit_anim_running: Arc::new(AtomicBool::new(false)),
356359
backtrack: BacktrackState::default(),
360+
#[cfg(feature = "feedback")]
357361
feedback: feedback.clone(),
358362
pending_update_action: None,
359363
suppress_shutdown_complete: false,
@@ -477,6 +481,7 @@ impl App {
477481
initial_images: Vec::new(),
478482
enhanced_keys_supported: self.enhanced_keys_supported,
479483
auth_manager: self.auth_manager.clone(),
484+
#[cfg(feature = "feedback")]
480485
feedback: self.feedback.clone(),
481486
expected_model: None, // No filtering for /new command
482487
};
@@ -611,12 +616,14 @@ impl App {
611616
failed_scan,
612617
);
613618
}
619+
#[cfg(feature = "feedback")]
614620
AppEvent::OpenFeedbackNote {
615621
category,
616622
include_logs,
617623
} => {
618624
self.chat_widget.open_feedback_note(category, include_logs);
619625
}
626+
#[cfg(feature = "feedback")]
620627
AppEvent::OpenFeedbackConsent { category } => {
621628
self.chat_widget.open_feedback_consent(category);
622629
}
@@ -947,6 +954,7 @@ impl App {
947954
initial_images: image_paths,
948955
enhanced_keys_supported: self.enhanced_keys_supported,
949956
auth_manager: self.auth_manager.clone(),
957+
#[cfg(feature = "feedback")]
950958
feedback: self.feedback.clone(),
951959
expected_model: Some(model_name.clone()),
952960
};
@@ -1140,6 +1148,7 @@ mod tests {
11401148
enhanced_keys_supported: false,
11411149
commit_anim_running: Arc::new(AtomicBool::new(false)),
11421150
backtrack: BacktrackState::default(),
1151+
#[cfg(feature = "feedback")]
11431152
feedback: codex_feedback::CodexFeedback::new(),
11441153
pending_update_action: None,
11451154
suppress_shutdown_complete: false,
@@ -1178,6 +1187,7 @@ mod tests {
11781187
enhanced_keys_supported: false,
11791188
commit_anim_running: Arc::new(AtomicBool::new(false)),
11801189
backtrack: BacktrackState::default(),
1190+
#[cfg(feature = "feedback")]
11811191
feedback: codex_feedback::CodexFeedback::new(),
11821192
pending_update_action: None,
11831193
suppress_shutdown_complete: false,

codex-rs/tui/src/app_backtrack.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,7 @@ impl App {
346346
initial_images: Vec::new(),
347347
enhanced_keys_supported: self.enhanced_keys_supported,
348348
auth_manager: self.auth_manager.clone(),
349+
#[cfg(feature = "feedback")]
349350
feedback: self.feedback.clone(),
350351
expected_model: None, // No filtering for backtracked conversations
351352
};

codex-rs/tui/src/app_event.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,12 +157,14 @@ pub(crate) enum AppEvent {
157157
FullScreenApprovalRequest(ApprovalRequest),
158158

159159
/// Open the feedback note entry overlay after the user selects a category.
160+
#[cfg(feature = "feedback")]
160161
OpenFeedbackNote {
161162
category: FeedbackCategory,
162163
include_logs: bool,
163164
},
164165

165166
/// Open the upload consent popup for feedback after selecting a category.
167+
#[cfg(feature = "feedback")]
166168
OpenFeedbackConsent {
167169
category: FeedbackCategory,
168170
},

codex-rs/tui/src/bottom_pane/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,19 @@ mod footer;
2828
mod list_selection_view;
2929
mod prompt_args;
3030
pub(crate) use list_selection_view::SelectionViewParams;
31+
#[cfg(feature = "feedback")]
3132
mod feedback_view;
33+
#[cfg(feature = "feedback")]
3234
pub(crate) use feedback_view::feedback_selection_params;
35+
#[cfg(feature = "feedback")]
3336
pub(crate) use feedback_view::feedback_upload_consent_params;
3437
mod paste_burst;
3538
pub mod popup_consts;
3639
mod queued_user_messages;
3740
mod scroll_state;
3841
mod selection_popup_common;
3942
mod textarea;
43+
#[cfg(feature = "feedback")]
4044
pub(crate) use feedback_view::FeedbackNoteView;
4145

4246
#[derive(Debug, Clone, Copy, PartialEq, Eq)]

codex-rs/tui/src/chatwidget.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use std::sync::Arc;
66
use std::time::Duration;
77

88
use codex_app_server_protocol::AuthMode;
9+
#[cfg(feature = "backend-client")]
910
use codex_backend_client::Client as BackendClient;
1011
use codex_core::config::Config;
1112
use codex_core::config::types::Notifications;
@@ -255,6 +256,7 @@ pub(crate) struct ChatWidgetInit {
255256
pub(crate) initial_images: Vec<PathBuf>,
256257
pub(crate) enhanced_keys_supported: bool,
257258
pub(crate) auth_manager: Arc<AuthManager>,
259+
#[cfg(feature = "feedback")]
258260
pub(crate) feedback: codex_feedback::CodexFeedback,
259261
/// Expected model name for this widget. When set, events from other models
260262
/// (e.g., from a previous agent) are ignored until SessionConfigured arrives
@@ -321,6 +323,7 @@ pub(crate) struct ChatWidget {
321323

322324
last_rendered_width: std::cell::Cell<Option<usize>>,
323325
// Feedback sink for /feedback
326+
#[cfg(feature = "feedback")]
324327
feedback: codex_feedback::CodexFeedback,
325328
// Current session rollout path (if known)
326329
current_rollout_path: Option<PathBuf>,
@@ -419,6 +422,7 @@ impl ChatWidget {
419422
}
420423
}
421424

425+
#[cfg(feature = "feedback")]
422426
pub(crate) fn open_feedback_note(
423427
&mut self,
424428
category: crate::app_event::FeedbackCategory,
@@ -442,6 +446,7 @@ impl ChatWidget {
442446
self.request_redraw();
443447
}
444448

449+
#[cfg(feature = "feedback")]
445450
pub(crate) fn open_feedback_consent(&mut self, category: crate::app_event::FeedbackCategory) {
446451
let params = crate::bottom_pane::feedback_upload_consent_params(
447452
self.app_event_tx.clone(),
@@ -1249,6 +1254,7 @@ impl ChatWidget {
12491254
initial_images,
12501255
enhanced_keys_supported,
12511256
auth_manager,
1257+
#[cfg(feature = "feedback")]
12521258
feedback,
12531259
expected_model,
12541260
} = common;
@@ -1302,6 +1308,7 @@ impl ChatWidget {
13021308
pre_review_token_info: None,
13031309
needs_final_message_separator: false,
13041310
last_rendered_width: std::cell::Cell::new(None),
1311+
#[cfg(feature = "feedback")]
13051312
feedback,
13061313
current_rollout_path: None,
13071314
pending_exec_cells: PendingExecCellTracker::new(),
@@ -1329,6 +1336,7 @@ impl ChatWidget {
13291336
initial_images,
13301337
enhanced_keys_supported,
13311338
auth_manager,
1339+
#[cfg(feature = "feedback")]
13321340
feedback,
13331341
expected_model,
13341342
} = common;
@@ -1384,6 +1392,7 @@ impl ChatWidget {
13841392
pre_review_token_info: None,
13851393
needs_final_message_separator: false,
13861394
last_rendered_width: std::cell::Cell::new(None),
1395+
#[cfg(feature = "feedback")]
13871396
feedback,
13881397
current_rollout_path: None,
13891398
pending_exec_cells: PendingExecCellTracker::new(),
@@ -1506,13 +1515,20 @@ impl ChatWidget {
15061515
return;
15071516
}
15081517
match cmd {
1518+
#[cfg(feature = "feedback")]
15091519
SlashCommand::Feedback => {
15101520
// Step 1: pick a category (UI built in feedback_view)
15111521
let params =
15121522
crate::bottom_pane::feedback_selection_params(self.app_event_tx.clone());
15131523
self.bottom_pane.show_selection_view(params);
15141524
self.request_redraw();
15151525
}
1526+
#[cfg(not(feature = "feedback"))]
1527+
SlashCommand::Feedback => {
1528+
// Show Nori-specific feedback message instead
1529+
use crate::nori::feedback;
1530+
self.add_info_message(feedback::feedback_message().to_string(), None);
1531+
}
15161532
SlashCommand::New => {
15171533
self.app_event_tx.send(AppEvent::NewSession);
15181534
}
@@ -2087,6 +2103,7 @@ impl ChatWidget {
20872103
}
20882104
}
20892105

2106+
#[cfg(feature = "backend-client")]
20902107
fn prefetch_rate_limits(&mut self) {
20912108
self.stop_rate_limit_poller();
20922109

@@ -2114,6 +2131,11 @@ impl ChatWidget {
21142131
self.rate_limit_poller = Some(handle);
21152132
}
21162133

2134+
#[cfg(not(feature = "backend-client"))]
2135+
fn prefetch_rate_limits(&mut self) {
2136+
// Rate limit prefetching requires backend-client feature
2137+
}
2138+
21172139
fn lower_cost_preset(&self) -> Option<ModelPreset> {
21182140
let auth_mode = self.auth_manager.auth().map(|auth| auth.mode);
21192141
builtin_model_presets(auth_mode)
@@ -3358,6 +3380,7 @@ fn extract_first_bold(s: &str) -> Option<String> {
33583380
None
33593381
}
33603382

3383+
#[cfg(feature = "backend-client")]
33613384
async fn fetch_rate_limits(base_url: String, auth: CodexAuth) -> Option<RateLimitSnapshot> {
33623385
match BackendClient::from_auth(base_url, &auth).await {
33633386
Ok(client) => match client.get_rate_limits().await {

codex-rs/tui/src/chatwidget/tests.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,7 @@ async fn helpers_are_available_and_do_not_panic() {
313313
initial_images: Vec::new(),
314314
enhanced_keys_supported: false,
315315
auth_manager,
316+
#[cfg(feature = "feedback")]
316317
feedback: codex_feedback::CodexFeedback::new(),
317318
expected_model: None,
318319
};
@@ -376,6 +377,7 @@ fn make_chatwidget_manual() -> (
376377
pre_review_token_info: None,
377378
needs_final_message_separator: false,
378379
last_rendered_width: std::cell::Cell::new(None),
380+
#[cfg(feature = "feedback")]
379381
feedback: codex_feedback::CodexFeedback::new(),
380382
current_rollout_path: None,
381383
pending_exec_cells: PendingExecCellTracker::new(),
@@ -1747,6 +1749,7 @@ fn single_reasoning_option_skips_selection() {
17471749
);
17481750
}
17491751

1752+
#[cfg(feature = "feedback")]
17501753
#[test]
17511754
fn feedback_selection_popup_snapshot() {
17521755
let (mut chat, _rx, _op_rx) = make_chatwidget_manual();
@@ -1758,6 +1761,7 @@ fn feedback_selection_popup_snapshot() {
17581761
assert_snapshot!("feedback_selection_popup", popup);
17591762
}
17601763

1764+
#[cfg(feature = "feedback")]
17611765
#[test]
17621766
fn feedback_upload_consent_popup_snapshot() {
17631767
let (mut chat, _rx, _op_rx) = make_chatwidget_manual();

0 commit comments

Comments
 (0)