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
+99 -1
View File
@@ -279,6 +279,19 @@ parse:
# caret pointer (visualising the failure column) is printed
# on its own preceding line via `parse.caret`.
error: "parse error: {detail}"
# Custom (try_map / source-slice) error messages raised by
# the DSL parser. These were hand-written strings in
# `src/dsl/parser.rs` until the catalog migration brought
# them under the same roof as the rest of the user-facing
# vocabulary. Wording is unchanged from the inline source
# form so existing anchor-phrase tests still match.
custom:
replay_path_expected: "expected a path after `replay`"
create_table_needs_pk: |-
tables need at least one column. Add `with pk` for a default `id INTEGER PRIMARY KEY`, or `with pk <name>:<type>` to choose. Use a comma-separated list for compound primary keys.
on_action_specified_twice: "`on {target}` specified twice"
change_column_flags_exclusive: "`--force-conversion` and `--dont-convert` are mutually exclusive — pick one."
unknown_type: "unknown type '{found}' (expected one of: {expected})"
# Caret pointer showing where in the input the parser
# failed. `{padding}` is the leading whitespace; the
# template appends `^` so the rendered line places the
@@ -322,6 +335,22 @@ parse:
update: "update <Table> set <col>=<value>[, ...] (where <col>=<value> | --all-rows)"
delete: "delete from <Table> (where <col>=<value> | --all-rows)"
replay: "replay <path> | replay '<path with spaces>'"
# App-lifecycle commands (per ADR-0003, surfaced through
# the parser so they participate in usage templates +
# completion). Templates here describe the surface
# grammar that the parser accepts; the in-app `help`
# listing in `help.in_app_body` carries the user-facing
# description.
quit: "quit | q"
help: "help"
rebuild: "rebuild"
save: "save | save as"
new: "new"
load: "load"
export: "export [<path>]"
import: "import <zip-path> [as <target>]"
mode: "mode simple | mode advanced"
messages: "messages | messages short | messages verbose"
# Single-token vocabulary the renderer uses to translate
# chumsky's expected-set patterns. One key per Keyword variant
# (validated against `Keyword::ALL`), one per Punct variant,
@@ -360,6 +389,23 @@ parse:
restrict: "`restrict`"
action: "`action`"
"no": "`no`"
# App-lifecycle commands (per ADR-0003, surfaced through
# the parser to drive completion + usage templates).
quit: "`quit`"
q: "`q`"
help: "`help`"
rebuild: "`rebuild`"
save: "`save`"
new: "`new`"
load: "`load`"
export: "`export`"
import: "`import`"
mode: "`mode`"
messages: "`messages`"
simple: "`simple`"
advanced: "`advanced`"
short: "`short`"
verbose: "`verbose`"
punct:
colon: "`:`"
open_paren: "`(`"
@@ -395,6 +441,12 @@ project:
load_path_missing: "path `{path}` does not exist"
saveas_target_exists: "`{path}` already exists; pick a different name or remove it first"
import_zip_missing: "zip `{path}` does not exist"
# --resume CLI failures printed to stderr before the TUI
# starts (ADR-0015 §7). Wording stays one line for clean
# piping; the runtime prepends `rdbms-playground: ` from
# `cli.binary_prefix` itself.
resume_recorded_missing: "--resume: recorded project `{path}` no longer exists"
resume_no_previous: "--resume: no previous project recorded under `{data_root}`"
# ---- DSL failure wrapper + advanced-mode placeholder + fatal --------
dsl:
@@ -447,7 +499,11 @@ status:
panel:
tables_title: "Tables"
tables_empty: "(none yet)"
hint_empty: "(no active hint)"
hint_empty: "Type a command — press Tab for options, `help` for a list"
# Panel titles for the output and hint panels (rendered inside
# the rounded border, hence the leading/trailing space).
output_title: "Output"
hint_title: "Hint"
# ---- Shortcut hints (paired with key names in the bottom bar) -------
shortcut:
@@ -473,6 +529,12 @@ mode:
show_advanced: "mode: advanced"
usage: "usage: mode simple | mode advanced"
unknown: "unknown mode '{value}' (expected 'simple' or 'advanced')"
# Labels rendered inside the input panel's border to mark the
# current input mode. `label_advanced_one_shot` is shown
# while a `:` one-shot is in flight from simple mode.
label_simple: "SIMPLE"
label_advanced: "ADVANCED"
label_advanced_one_shot: "Advanced:"
messages:
show: "messages: {current}"
@@ -480,6 +542,42 @@ messages:
set_verbose: "messages: verbose"
unknown: "unknown messages mode '{value}' (expected 'short' or 'verbose')"
# ---- Cascade-effect summaries (per ADR-0014 delete reporting) -------
db:
cascade:
# Per-relationship cascade summary appended to a delete
# success note. The same template handles cascade,
# set-null, and restrict/no-action cases — `{action}` is
# one of the three action phrases below.
summary: " related: {count} row(s) {action} in `{child_table}` for relationship `{rel}` (on delete {on_delete})"
action_deleted: "deleted"
action_set_null: "had FK set to null"
action_blocked: "blocked"
# `change column ... (newtype)` dry-run diagnostics (ADR-0017).
# Surface when the migration would lose information (lossy),
# produce values the target type can't represent
# (incompatible), or violate a uniqueness contract (collision).
diagnostic:
# Column headers for the diagnostic tables.
header_from: "From"
header_to: "To"
header_reason: "Reason"
header_value: "Value"
header_becomes: "Becomes"
header_source_rows: "Source rows ({pk_label})"
header_source_values: "Source values"
# Summary lines printed above each diagnostic table.
lossy_summary: |-
Cannot change `{table}.{column}` from {src_ty} to {target_ty}: {total} row(s) would discard information.
incompatible_summary: |-
Cannot change `{table}.{column}` from {src_ty} to {target_ty}: {total} row(s) cannot be converted.
uniqueness_summary: |-
Cannot change `{table}.{column}` from {src_ty} to {target_ty}: {total} collision(s) would violate uniqueness.
# Follow-up suggestion appended to the lossy diagnostic
# (only — incompatibles can't be force-overridden).
force_conversion_hint: "if you want to execute this conversion in spite of the problems, re-run with `--force-conversion`."
# ---- DSL command success summaries (ADR-0019 §9 sweep) --------------
ok:
# Generic `[ok] <verb> <subject>` header used for every