feat(cli): --demo demonstration mode flag + app plumbing (#22, ADR-0047 D1)

Add `--demo` (and the RDBMS_PLAYGROUND_DEMO env fallback) to enter
demonstration mode, threaded onto App.demo_mode through run_loop —
mirrors the --no-undo plumbing. Off by default, zero footprint when
off. The --help line advertises only the visible keystroke badges;
the Ctrl+] caption trigger is kept low-profile (ADR-0047 D6 updated).

Phase A of ADR-0047; behaviour (badges/captions) lands in B and C.
This commit is contained in:
claude@clouddev1
2026-06-10 22:22:12 +00:00
parent e9eb1b177e
commit f879d54721
5 changed files with 107 additions and 4 deletions
+77
View File
@@ -42,6 +42,13 @@ pub struct Args {
/// mode > the default (`simple`). Combines with `--resume` and
/// a positional path; on collision the flag wins.
pub mode: Option<Mode>,
/// `--demo` (or `RDBMS_PLAYGROUND_DEMO` set truthy): enter
/// **demonstration mode** (ADR-0047, issue #22). Off by default,
/// zero footprint when off. When on, the app shows transient
/// on-screen badges for otherwise-invisible keys (Tab, Enter, …)
/// and enables the `Ctrl+]` stealth step-caption buffer — for
/// screencasts and live teaching. The flag wins over the env var.
pub demo: bool,
}
/// Usage banner printed by `--help`.
@@ -124,6 +131,12 @@ impl Args {
let mut help = false;
let mut no_undo = false;
let mut mode: Option<Mode> = None;
// Demonstration mode (ADR-0047): the env var is the default,
// the `--demo` flag overrides it to on. Mirrors the
// env-then-flag layering used for the log file above.
let mut demo = env::var("RDBMS_PLAYGROUND_DEMO")
.ok()
.is_some_and(|v| demo_value_is_truthy(&v));
let mut iter = iter.into_iter().map(Into::into);
while let Some(arg) = iter.next() {
match arg.as_str() {
@@ -136,6 +149,9 @@ impl Args {
"--no-undo" => {
no_undo = true;
}
"--demo" => {
demo = true;
}
"--theme" => {
let value = iter.next().ok_or(ArgsError::MissingValue("theme"))?;
theme = match value.as_str() {
@@ -194,10 +210,25 @@ impl Args {
help,
no_undo,
mode,
demo,
})
}
}
/// Whether a `RDBMS_PLAYGROUND_DEMO` value enables demo mode.
///
/// Truthy for any value except the conventional "off" set
/// (`0`/`false`/`no`/`off`, case-insensitively, and the empty
/// string). So `RDBMS_PLAYGROUND_DEMO=1` and `=true` enable, while
/// `=0` / `=false` explicitly disable — letting a value of `0` turn
/// it off even if something upstream exported the variable.
fn demo_value_is_truthy(value: &str) -> bool {
!matches!(
value.trim().to_ascii_lowercase().as_str(),
"" | "0" | "false" | "no" | "off"
)
}
fn default_theme() -> Theme {
// NFR-7: support both backgrounds. For the walking skeleton we
// honour an explicit `--theme` flag and the COLORFGBG env var
@@ -391,6 +422,52 @@ mod tests {
);
}
// ---- ADR-0047 (issue #22): --demo demonstration mode ----
#[test]
fn demo_flag_parses() {
let args = Args::parse(["--demo"]).unwrap();
assert!(args.demo);
}
#[test]
fn demo_defaults_off() {
// Absent `--demo` (and absent env var in the test runner),
// demo mode is off — zero footprint for real users.
let args = Args::parse(std::iter::empty::<&str>()).unwrap();
assert!(!args.demo, "demo is off unless --demo or the env var is given");
}
#[test]
fn demo_flag_coexists_with_positional_path() {
let args = Args::parse(["--demo", "/home/me/MyProject"]).unwrap();
assert!(args.demo);
assert_eq!(
args.project_path.as_deref(),
Some(std::path::Path::new("/home/me/MyProject"))
);
}
#[test]
fn demo_flag_combines_with_resume_and_mode() {
let args = Args::parse(["--resume", "--demo", "--mode", "advanced"]).unwrap();
assert!(args.demo);
assert!(args.resume);
assert_eq!(args.mode, Some(Mode::Advanced));
}
#[test]
fn demo_env_value_truthiness() {
// Enabling values.
for v in ["1", "true", "TRUE", "yes", "on", "anything", " 1 "] {
assert!(demo_value_is_truthy(v), "{v:?} should enable demo mode");
}
// Disabling values.
for v in ["", " ", "0", "false", "False", "no", "off", "OFF"] {
assert!(!demo_value_is_truthy(v), "{v:?} should not enable demo mode");
}
}
#[test]
fn unknown_double_dash_flag_errors_even_with_positional() {
// Make sure the path-vs-flag distinction is robust: