app: mode-threaded completion, overlay, and validity indicator

The dispatch-layer mode gate (previous commit) made the submit
behaviour correct — `select` runs in advanced mode and shows
the SQL hint in simple mode. This commit extends that gating to
the ambient assistance layer so simple-mode users do not see
SQL leak through Tab completion, the live error overlay, or the
`[ERR]`/`[WRN]` validity indicator either.

`_in_mode` walker variants
--------------------------
- `completion_probe_in_mode`, `expected_at_input_in_mode`,
  `input_verdict_in_mode`. Each sets `ctx.mode` before walking.
  The empty-input / unknown-entry fallback in `completion_probe`
  and `expected_at_input` filters the `REGISTRY` listing by
  `is_advanced_only` so Tab does not offer `select` in simple
  mode. Old signatures keep delegating to `Mode::Advanced`
  (back-compat for tests + other callers).

`_in_mode` completion variants
------------------------------
- `candidates_at_cursor_in_mode`, `candidates_at_cursor_with_in_mode`.
  Internally they route the `parse_command` completeness probe
  through `parse_command_in_mode(input, mode)`, the
  `completion_probe` call through `completion_probe_in_mode`,
  and the `expected_at` fallback through
  `expected_at_input_in_mode`. Old signatures default to
  `Mode::Advanced`.

`EffectiveMode::as_mode`
------------------------
- Collapses the persistent / one-shot distinction the UI cares
  about into the plain `Mode` the walker reads from
  `WalkContext::mode`. App-level call sites that thread mode
  into the walker chain use this.

App / input-render wiring
-------------------------
- `App::input_validity_verdict` runs only when effective mode
  is plain `Simple` (per ADR-0027), so it hardcodes
  `Mode::Simple` into the new `input_verdict_in_mode` call
  rather than threading.
- `App::start_or_complete_at` / `_last` (the Tab handlers)
  pass `self.effective_mode().as_mode()` into
  `candidates_at_cursor_in_mode`, so a `:` one-shot or
  persistent advanced gives full SQL completion, persistent
  simple does not offer SQL.
- `input_render::render_input_runs` and `ambient_hint` are
  invoked from `ui.rs` only when effective mode is plain
  `Simple` (advanced rendering uses `plain_input_spans` and
  skips ambient hinting per ADR-0022 §12). Their internal
  `classify_input_with_schema` / `candidates_at_cursor` /
  `parse_command` calls now go through the mode-aware variants
  with `Mode::Simple` hardcoded — a SQL form in simple mode
  surfaces as a definite-error overlay and the hint panel does
  not offer it.

After this commit a simple-mode user typing `select` or
`sel<Tab>` sees nothing SQL-shaped: no live highlight, no Tab
completion candidate, the `[ERR]` indicator lit, and the on-
submit hint that names the recovery paths. An advanced-mode
user or a `:` one-shot sees the full SQL surface.
This commit is contained in:
claude@clouddev1
2026-05-19 21:48:21 +00:00
parent 6369066fe4
commit 83e0ddc2ff
4 changed files with 157 additions and 35 deletions
+25 -3
View File
@@ -110,6 +110,18 @@ impl EffectiveMode {
pub const fn is_advanced(self) -> bool {
matches!(self, Self::AdvancedPersistent | Self::AdvancedOneShot)
}
/// Collapse the persistent/one-shot distinction the UI cares
/// about into the plain [`Mode`] the walker reads from
/// `WalkContext::mode` (ADR-0030 §2). Both advanced variants
/// map to `Mode::Advanced`.
#[must_use]
pub const fn as_mode(self) -> Mode {
match self {
Self::Simple => Mode::Simple,
Self::AdvancedPersistent | Self::AdvancedOneShot => Mode::Advanced,
}
}
}
#[derive(Debug)]
@@ -378,7 +390,15 @@ impl App {
if !matches!(self.effective_mode(), EffectiveMode::Simple) {
return None;
}
crate::dsl::walker::input_verdict(&self.input, Some(&self.schema_cache))
// ADR-0030 §2: the indicator is shown only in plain
// simple mode (the guard above), so the verdict reads
// the simple-mode walker view — a SQL form lights up
// ERROR via the walker's mode gate.
crate::dsl::walker::input_verdict_in_mode(
&self.input,
Some(&self.schema_cache),
Mode::Simple,
)
}
/// Process one event from the runtime, mutating state and
@@ -782,10 +802,11 @@ impl App {
fn start_or_complete_at(&mut self, multi_start_idx: usize) {
let cursor = self.input_cursor.min(self.input.len());
let Some(comp) = crate::completion::candidates_at_cursor(
let Some(comp) = crate::completion::candidates_at_cursor_in_mode(
&self.input,
cursor,
&self.schema_cache,
self.effective_mode().as_mode(),
) else {
return;
};
@@ -799,10 +820,11 @@ impl App {
fn start_or_complete_last(&mut self) {
let cursor = self.input_cursor.min(self.input.len());
let Some(comp) = crate::completion::candidates_at_cursor(
let Some(comp) = crate::completion::candidates_at_cursor_in_mode(
&self.input,
cursor,
&self.schema_cache,
self.effective_mode().as_mode(),
) else {
return;
};