fix: H1a G3 advanced usage shows all valid forms; complete near-miss matrix (ADR-0042)
The /runda DA pass found G3 over-corrected: advanced-mode `create`/`drop` showed SQL forms only, hiding the DSL fallback forms that are valid input in advanced mode (verified: `create table Foo with pk`, `drop column …` parse and dispatch). Per the user decision, the advanced usage block now shows every form valid in the mode, SQL-primary first, then the DSL fallback forms — a usage hint must never hide working input. Simple mode unchanged (DSL forms only). Matrix completion (closing the residual coverage tail): - arg-less app commands (help/rebuild/new/load/undo/redo/export/import) audited + locked — all reject trailing junk with "expected end of input" + usage. - committed multi-forms (add index/constraint/1:n relationship, drop index/constraint/relationship, show table, change column, create index, alter table add/drop) audited + locked in near_miss_matrix_committed_multiforms — each renders its own form-specific missing-keyword message + usage. Also from the DA pass: - G2 distinct+all detector empirically verified unique to projection start (no misfire at count( / union / union all / select distinct). - stale `chumsky` comment removed (app.rs import handler). - ADR-0042 Implementation-outcome section records G1–G4, the user-confirmed G3 decision, and the now-complete matrix coverage. Full suite green (lib 1578 / it 387 / typing_surface_matrix 192); clippy clean.
This commit is contained in:
+5
-5
@@ -1338,11 +1338,11 @@ impl App {
|
||||
},
|
||||
),
|
||||
AppCommand::Import { path, target } => {
|
||||
// The path-bearing import goes through the
|
||||
// pre-chumsky source-slice (parser.rs), which
|
||||
// already validated non-empty path. Bare
|
||||
// `import` returns from chumsky with an empty
|
||||
// path string — surface the usage error.
|
||||
// A path-bearing import carries a non-empty path
|
||||
// from the walker. Bare `import` parses with an
|
||||
// empty path string — surface the usage hint here
|
||||
// at dispatch (not a parse error; ADR-0024 replaced
|
||||
// the old chumsky source-slice path).
|
||||
if path.is_empty() {
|
||||
self.note_error(crate::t!("project.import_usage"));
|
||||
return Vec::new();
|
||||
|
||||
+35
-19
@@ -566,11 +566,6 @@ pub fn usage_keys_for_input_in_mode(
|
||||
if candidates.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let want = if mode == crate::mode::Mode::Advanced {
|
||||
CommandCategory::Advanced
|
||||
} else {
|
||||
CommandCategory::Simple
|
||||
};
|
||||
let union = |nodes: &[(usize, &'static CommandNode, CommandCategory)]| -> Vec<&'static str> {
|
||||
let mut keys: Vec<&'static str> = Vec::new();
|
||||
for (_, node, _) in nodes {
|
||||
@@ -582,23 +577,44 @@ pub fn usage_keys_for_input_in_mode(
|
||||
}
|
||||
keys
|
||||
};
|
||||
let matched: Vec<(usize, &'static CommandNode, CommandCategory)> =
|
||||
candidates.iter().copied().filter(|(_, _, cat)| *cat == want).collect();
|
||||
// Prefer the mode-matching nodes' usage. But a shared SQL node
|
||||
// (`SQL_INSERT` / `SQL_UPDATE` / `SQL_DELETE`) declares no
|
||||
// `usage_ids` of its own — it reuses the DSL template. When the
|
||||
// mode-preferred set yields no usage keys, fall back to every
|
||||
// candidate so the entry word still shows a usage block rather
|
||||
// than the available-commands fallback (regression-locked by
|
||||
// the advanced near-miss matrix).
|
||||
let mut keys = union(&matched);
|
||||
if keys.is_empty() {
|
||||
keys = union(&candidates);
|
||||
}
|
||||
// Advanced mode: every candidate form is reachable — the SQL
|
||||
// nodes are primary, and the DSL nodes remain valid via fallback
|
||||
// (verified: `create table … with pk` and `drop column …` both
|
||||
// run in advanced mode). Show them all, mode-primary (Advanced)
|
||||
// first, so the usage hint never hides input that works. Simple
|
||||
// mode: only the DSL forms — the SQL-only forms hit the "this is
|
||||
// SQL" rail and are not reachable. (ADR-0042 G3.)
|
||||
let selected: Vec<(usize, &'static CommandNode, CommandCategory)> =
|
||||
if mode == crate::mode::Mode::Advanced {
|
||||
let mut v: Vec<_> = candidates
|
||||
.iter()
|
||||
.copied()
|
||||
.filter(|(_, _, c)| *c == CommandCategory::Advanced)
|
||||
.collect();
|
||||
v.extend(
|
||||
candidates
|
||||
.iter()
|
||||
.copied()
|
||||
.filter(|(_, _, c)| *c != CommandCategory::Advanced),
|
||||
);
|
||||
v
|
||||
} else {
|
||||
candidates
|
||||
.iter()
|
||||
.copied()
|
||||
.filter(|(_, _, c)| *c == CommandCategory::Simple)
|
||||
.collect()
|
||||
};
|
||||
// Degenerate guard: an advanced-only word in simple mode (not
|
||||
// normally reachable — it hits the SQL rail first) leaves
|
||||
// `selected` empty; fall back to all candidates so a usage block
|
||||
// still renders rather than the available-commands fallback.
|
||||
let pick = if selected.is_empty() { candidates } else { selected };
|
||||
let keys = union(&pick);
|
||||
if keys.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let entry = candidates[0].1.entry.primary;
|
||||
let entry = pick[0].1.entry.primary;
|
||||
Some((entry, keys))
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user