Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion codex-rs/core/src/features.rs
Original file line number Diff line number Diff line change
Expand Up @@ -460,7 +460,11 @@ pub const FEATURES: &[FeatureSpec] = &[
FeatureSpec {
id: Feature::JsRepl,
key: "js_repl",
stage: Stage::UnderDevelopment,
stage: Stage::Experimental {
name: "JavaScript REPL",
menu_description: "Enable a persistent Node-backed JavaScript REPL for interactive website debugging and other inline JavaScript execution capabilities. Requires Node >= v24.13.1 installed.",
announcement: "NEW: JavaScript REPL is now available in /experimental. Enable it, then start a new chat or restart Codex to use it.",
},
default_enabled: false,
},
FeatureSpec {
Expand Down Expand Up @@ -795,6 +799,23 @@ mod tests {
assert_eq!(Feature::UseLinuxSandboxBwrap.default_enabled(), false);
}

#[test]
fn js_repl_is_experimental_and_user_toggleable() {
let spec = Feature::JsRepl.info();
let stage = spec.stage;
let expected_node_version = include_str!("../../node-version.txt").trim_end();

assert!(matches!(stage, Stage::Experimental { .. }));
assert_eq!(stage.experimental_menu_name(), Some("JavaScript REPL"));
assert_eq!(
stage.experimental_menu_description().map(str::to_owned),
Some(format!(
"Enable a persistent Node-backed JavaScript REPL for interactive website debugging and other inline JavaScript execution capabilities. Requires Node >= v{expected_node_version} installed."
))
);
assert_eq!(Feature::JsRepl.default_enabled(), false);
}

#[test]
fn collab_is_legacy_alias_for_multi_agent() {
assert_eq!(feature_for_key("multi_agent"), Some(Feature::Collab));
Expand Down
25 changes: 25 additions & 0 deletions codex-rs/tui/src/chatwidget/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ use codex_core::config::ConstraintError;
#[cfg(target_os = "windows")]
use codex_core::config::types::WindowsSandboxModeToml;
use codex_core::config_loader::RequirementSource;
use codex_core::features::FEATURES;
use codex_core::features::Feature;
use codex_core::models_manager::manager::ModelsManager;
use codex_core::skills::model::SkillMetadata;
Expand Down Expand Up @@ -5945,6 +5946,30 @@ async fn experimental_features_toggle_saves_on_exit() {
assert_eq!(updates, vec![(expected_feature, true)]);
}

#[tokio::test]
async fn experimental_popup_shows_js_repl_node_requirement() {
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None).await;

let js_repl_description = FEATURES
.iter()
.find(|spec| spec.id == Feature::JsRepl)
.and_then(|spec| spec.stage.experimental_menu_description())
.expect("expected js_repl experimental description");
let node_requirement = js_repl_description
.split(". ")
.find(|sentence| sentence.starts_with("Requires Node >= v"))
.map(|sentence| sentence.trim_end_matches(" installed."))
.expect("expected js_repl description to mention the Node requirement");

chat.open_experimental_popup();

let popup = render_bottom_popup(&chat, 120);
assert!(
popup.contains(node_requirement),
"expected js_repl feature description to mention the required Node version, got:\n{popup}"
);
}

#[tokio::test]
async fn model_selection_popup_snapshot() {
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5-codex")).await;
Expand Down
Loading