runtime: debounce the validity indicator (ADR-0027 step E)

The event loop now time-boxes `recv` while an indicator
recompute is owed: every keystroke hides the indicator and
arms an `INDICATOR_DEBOUNCE` (1s) window; once typing pauses
that long the runtime computes `App::input_validity_verdict`
and shows `[ERR]` / `[WRN]`. An idle session (nothing owed)
still blocks plainly on `recv` — no wake-ups.

`update()` stays pure — the debounce timer lives in the
runtime; `App` only holds the resulting `input_indicator`
state, which the runtime clears on a keystroke and sets when
the quiet interval elapses.

`App::input_validity_verdict` is tested directly (a
simple-mode verdict, and silence in advanced mode / the `:`
one-shot); the debounce timing itself is runtime-loop glue,
covered at the integration level.
This commit is contained in:
claude@clouddev1
2026-05-19 07:30:47 +00:00
parent 1a9d950cc2
commit 9e10997ffd
2 changed files with 77 additions and 1 deletions
+37
View File
@@ -3042,4 +3042,41 @@ mod tests {
},
);
}
// ---- Validity-indicator verdict (ADR-0027) ----------------
#[test]
fn input_validity_verdict_flags_a_broken_simple_command() {
let mut app = App::new();
app.input = "create table".to_string();
assert_eq!(
app.input_validity_verdict(),
Some(crate::dsl::walker::Severity::Error),
);
}
#[test]
fn input_validity_verdict_is_none_for_clean_input() {
let mut app = App::new();
app.input = "quit".to_string();
assert_eq!(app.input_validity_verdict(), None);
}
#[test]
fn input_validity_verdict_silent_in_advanced_mode() {
// Advanced mode is raw SQL — the DSL walker must not
// pass a verdict on it (ADR-0027 §7).
let mut app = App::new();
app.mode = Mode::Advanced;
app.input = "create table".to_string();
assert_eq!(app.input_validity_verdict(), None);
}
#[test]
fn input_validity_verdict_silent_for_colon_one_shot() {
// A `:`-prefixed line is a one-shot advanced escape.
let mut app = App::new();
app.input = ":create table".to_string();
assert_eq!(app.input_validity_verdict(), None);
}
}