round-5 follow-up: completion + i18n sweep

Four user-reported gaps from the round-4 testing pass:

1. Empty-prompt hint reworded from "(no active hint)" to
   "Type a command — press Tab for options, `help` for a
   list" (6 snapshots updated to reflect 80-col truncation).

2. App-lifecycle commands (quit/q, help, rebuild, save/save as,
   new, load, export, import, mode, messages) now flow through
   the DSL parser:
   - 15 new keywords + catalog token entries
   - new Command::App(AppCommand) AST with 11 variants
   - parse-first dispatch in submit() (app commands work in
     both simple and advanced modes)
   - pre-chumsky source-slice for `export <path>` /
     `import <zip> [as <target>]` mirrors the replay precedent
   - UsageEntry registry entries so parse errors surface
     relevant usage templates
   - `mode <bad>` / `messages <bad>` use try_map for the
     friendly "unknown mode/messages" wording

3. DSL completion gaps:
   - `1:n` surfaces as a composite candidate at `add `
   - --all-rows / --create-fk / --force-conversion /
     --dont-convert surface as new CandidateKind::Flag
     candidates (coloured with tok_flag in hint panel)
   - filter_clause .labelled() wrap removed so chumsky's
     expected-set surfaces the constituent options

4. Hardcoded user-facing strings migrated to catalog:
   - 4 parser custom errors (incl. the known "tables need at
     least one column" wart)
   - UnknownType Display now via parse.custom.unknown_type
   - UI panel titles + mode labels (Output / Hint / SIMPLE /
     ADVANCED / Advanced:)
   - app.rs cascade rendering (action labels + summary)
   - runtime --resume CLI stderr
   - db.rs change-column diagnostic tables (7 headers + 3
     wrapper summaries + force-conversion hint)

Tests: 765 → 769 passing, 0 failed, 1 ignored (same doctest
as before). Clippy clean with -D warnings.

Deferred:
- ~25 thiserror #[error] attributes still hand-rolled
  (DbError, ArgsError, ArchiveError, PersistenceError,
  LockError). Tracked separately.
- DSL/SQL relationship in advanced mode — clarified
  implicitly via parse-first dispatch; broader ADR
  amendment to follow.
- Post-complete-parse completion gap (e.g. `save ` Tab
  can't offer `as` because `save` parses bare; same shape
  as `--create-fk` after a complete `add relationship`).
This commit is contained in:
claude@clouddev1
2026-05-13 15:58:29 +00:00
parent 1eb2e0d01f
commit 1e06490572
22 changed files with 1077 additions and 189 deletions
+68
View File
@@ -140,6 +140,13 @@ pub const KEYS_AND_PLACEHOLDERS: &[(&str, &[&str])] = &[
// ---- Parse error rendering ----
("parse.available_commands", &["commands"]),
("parse.caret", &["padding"]),
// Custom (try_map / source-slice) error messages raised
// by the DSL parser. See `parse.custom.*` in the catalog.
("parse.custom.change_column_flags_exclusive", &[]),
("parse.custom.create_table_needs_pk", &[]),
("parse.custom.on_action_specified_twice", &["target"]),
("parse.custom.replay_path_expected", &[]),
("parse.custom.unknown_type", &["found", "expected"]),
("parse.empty", &[]),
("parse.error", &["detail"]),
// Per-command usage templates (ADR-0021 §1). One key per
@@ -158,7 +165,17 @@ pub const KEYS_AND_PLACEHOLDERS: &[(&str, &[&str])] = &[
("parse.usage.drop_table", &[]),
("parse.usage.insert", &[]),
("parse.usage.rename_column", &[]),
("parse.usage.export", &[]),
("parse.usage.help", &[]),
("parse.usage.import", &[]),
("parse.usage.load", &[]),
("parse.usage.messages", &[]),
("parse.usage.mode", &[]),
("parse.usage.new", &[]),
("parse.usage.quit", &[]),
("parse.usage.rebuild", &[]),
("parse.usage.replay", &[]),
("parse.usage.save", &[]),
("parse.usage.show_data", &[]),
("parse.usage.show_table", &[]),
("parse.usage.update", &[]),
@@ -176,6 +193,7 @@ pub const KEYS_AND_PLACEHOLDERS: &[(&str, &[&str])] = &[
("parse.token.identifier", &[]),
("parse.token.keyword.action", &[]),
("parse.token.keyword.add", &[]),
("parse.token.keyword.advanced", &[]),
("parse.token.keyword.as", &[]),
("parse.token.keyword.cascade", &[]),
("parse.token.keyword.change", &[]),
@@ -184,26 +202,40 @@ pub const KEYS_AND_PLACEHOLDERS: &[(&str, &[&str])] = &[
("parse.token.keyword.data", &[]),
("parse.token.keyword.delete", &[]),
("parse.token.keyword.drop", &[]),
("parse.token.keyword.export", &[]),
("parse.token.keyword.false", &[]),
("parse.token.keyword.from", &[]),
("parse.token.keyword.help", &[]),
("parse.token.keyword.import", &[]),
("parse.token.keyword.in", &[]),
("parse.token.keyword.insert", &[]),
("parse.token.keyword.into", &[]),
("parse.token.keyword.load", &[]),
("parse.token.keyword.messages", &[]),
("parse.token.keyword.mode", &[]),
("parse.token.keyword.new", &[]),
("parse.token.keyword.no", &[]),
("parse.token.keyword.null", &[]),
("parse.token.keyword.on", &[]),
("parse.token.keyword.pk", &[]),
("parse.token.keyword.q", &[]),
("parse.token.keyword.quit", &[]),
("parse.token.keyword.rebuild", &[]),
("parse.token.keyword.relationship", &[]),
("parse.token.keyword.rename", &[]),
("parse.token.keyword.replay", &[]),
("parse.token.keyword.restrict", &[]),
("parse.token.keyword.save", &[]),
("parse.token.keyword.set", &[]),
("parse.token.keyword.short", &[]),
("parse.token.keyword.show", &[]),
("parse.token.keyword.simple", &[]),
("parse.token.keyword.table", &[]),
("parse.token.keyword.to", &[]),
("parse.token.keyword.true", &[]),
("parse.token.keyword.update", &[]),
("parse.token.keyword.values", &[]),
("parse.token.keyword.verbose", &[]),
("parse.token.keyword.where", &[]),
("parse.token.keyword.with", &[]),
("parse.token.number", &[]),
@@ -222,6 +254,8 @@ pub const KEYS_AND_PLACEHOLDERS: &[(&str, &[&str])] = &[
("project.import_usage", &[]),
("project.import_zip_missing", &["path"]),
("project.load_path_missing", &["path"]),
("project.resume_no_previous", &["data_root"]),
("project.resume_recorded_missing", &["path"]),
("project.saveas_target_exists", &["path"]),
("project.rebuild_failed", &["error"]),
("project.rebuild_ok", &["summary"]),
@@ -248,6 +282,8 @@ pub const KEYS_AND_PLACEHOLDERS: &[(&str, &[&str])] = &[
("modal.rebuild_confirm_title", &[]),
// ---- Status bar + panels ----
("panel.hint_empty", &[]),
("panel.hint_title", &[]),
("panel.output_title", &[]),
("panel.tables_empty", &[]),
("panel.tables_title", &[]),
("status.no_project", &[]),
@@ -276,12 +312,44 @@ pub const KEYS_AND_PLACEHOLDERS: &[(&str, &[&str])] = &[
("messages.set_verbose", &[]),
("messages.show", &["current"]),
("messages.unknown", &["value"]),
("mode.label_advanced", &[]),
("mode.label_advanced_one_shot", &[]),
("mode.label_simple", &[]),
("mode.set_advanced", &[]),
("mode.set_simple", &[]),
("mode.show_advanced", &[]),
("mode.show_simple", &[]),
("mode.unknown", &["value"]),
("mode.usage", &[]),
// ---- Cascade-effect summaries (per ADR-0014) ----
("db.cascade.action_blocked", &[]),
("db.cascade.action_deleted", &[]),
("db.cascade.action_set_null", &[]),
(
"db.cascade.summary",
&["count", "action", "child_table", "rel", "on_delete"],
),
// ---- change-column dry-run diagnostics (per ADR-0017) ----
("db.diagnostic.force_conversion_hint", &[]),
("db.diagnostic.header_becomes", &[]),
("db.diagnostic.header_from", &[]),
("db.diagnostic.header_reason", &[]),
("db.diagnostic.header_source_rows", &["pk_label"]),
("db.diagnostic.header_source_values", &[]),
("db.diagnostic.header_to", &[]),
("db.diagnostic.header_value", &[]),
(
"db.diagnostic.incompatible_summary",
&["table", "column", "src_ty", "target_ty", "total"],
),
(
"db.diagnostic.lossy_summary",
&["table", "column", "src_ty", "target_ty", "total"],
),
(
"db.diagnostic.uniqueness_summary",
&["table", "column", "src_ty", "target_ty", "total"],
),
// ---- DSL command success summaries (ADR-0019 §9 sweep) ----
("ok.rows_deleted", &["count"]),
("ok.rows_inserted", &["count"]),