# en-US catalog (ADR-0019). # # Hierarchical groups flatten to dot-paths internally: # error.unique.insert.headline # error.unique.insert.hint # help.cli_banner # replay.completed # … etc. # # Each error entry has a `headline` (one line, used in both # short and verbose modes) and may have a `hint` (one or more # lines, surfaced only in verbose mode). Short mode = headline # only; verbose mode = headline + hint + (if present) the # diagnostic table the translator built (ADR-0019 §7). # # Anchor phrases per ADR-0019 §10 are kept stable across # wording changes: # "no such table" # "no such column" # "no such relationship" # "already exists" # "already has the value" # "cannot be converted" # "discard information" # "referenced by" # "[client-side]" # # Placeholders use `{name}` substitution; format specifiers # (`{name:08.2}`, `{name:>10}`, …) are explicitly rejected by # the substitution helper (ADR-0019 §8.4). # Sanity entry exercised by the loader's unit tests; not # user-facing. _test: hello: "Hello, {name}!" # ---- Error category -------------------------------------------------- error: # UNIQUE constraint violations. Anchor: "already has the value". unique: insert: headline: "`{table}.{column}` already has the value `{value}`." hint: "The `{column}` column on `{table}` is unique — pick a different value, or update the existing row instead." update: headline: "`{table}.{column}` already has the value `{value}`." hint: "The `{column}` column on `{table}` is unique — your update would create a duplicate." # Primary-key collisions get distinct wording — the user # learns that PK is the canonical unique constraint. pk: insert: headline: "`{table}` already has a row with primary key `{value}`." hint: "Primary keys must be unique — pick a different value or update the existing row." update: headline: "`{table}` already has a row with primary key `{value}`." hint: "Primary keys must be unique — your update would create a duplicate." # FOREIGN KEY violations. Anchor: "referenced by". foreign_key: # Child-side: insert/update sets a value that has no parent. child_side: insert: headline: "no parent row in `{parent_table}` has `{parent_column}` = `{value}`." hint: "Foreign keys must point at an existing parent row. Insert a matching parent first, or pick a value that already exists in `{parent_table}.{parent_column}`." update: headline: "no parent row in `{parent_table}` has `{parent_column}` = `{value}`." hint: "Foreign keys must point at an existing parent row. Pick a value that already exists in `{parent_table}.{parent_column}`." # Parent-side: delete/update on a row referenced by children. # The engine refuses unless the relationship's `on delete / # on update` says cascade or set null. parent_side: delete: headline: "`{table}` rows are referenced by `{child_table}`." hint: "Deleting these rows would orphan the children. Delete the children first, or change the relationship's `on delete` action to `cascade` or `set null`." update: headline: "`{table}` rows are referenced by `{child_table}`." hint: "Updating the referenced column would orphan the children. Update the children first, or change the relationship's `on update` action to `cascade`." # NOT NULL constraint violations. not_null: insert: headline: "`{table}.{column}` cannot be null." hint: "The `{column}` column is required — provide a value for it in the row you are inserting." update: headline: "`{table}.{column}` cannot be null." hint: "The `{column}` column is required — pick a non-null value, or do not include `{column}` in your `set` list." # CHECK constraint violations (ADR-0029 §10). When the # runtime resolves the column's compiled CHECK expression, # `hint_with_rule` names both the offending value and the # rule; the plain `hint` is the fallback when enrichment # could not resolve the rule. check: insert: headline: "check constraint refused `{table}.{column}`." hint: "A check constraint requires `{column}` to satisfy a rule the inserted value did not." hint_with_rule: "The value {value} breaks the rule `{rule}` — `{column}` must satisfy it." update: headline: "check constraint refused `{table}.{column}`." hint: "A check constraint requires `{column}` to satisfy a rule the new value did not." hint_with_rule: "The new value {value} breaks the rule `{rule}` — `{column}` must satisfy it." # Type mismatch — engine-side STRICT refusal of a wrong-shape # value. Mostly the `change column ... --dont-convert` path # today (subsumes `friendly_change_column_engine_error`). type_mismatch: change_column: headline: "cannot change `{table}.{column}` from `{src_type}` to `{target_type}` with `--dont-convert`." hint: "The database refused at least one cell as the wrong shape for `{target_type}`. Re-run without `--dont-convert` to see which rows." insert: headline: "value `{value}` is not a `{expected_type}`." hint: "The `{column}` column on `{table}` is `{expected_type}` — provide a `{expected_type}` value, or change the column's type with `change column`." update: headline: "value `{value}` is not a `{expected_type}`." hint: "The `{column}` column on `{table}` is `{expected_type}` — provide a `{expected_type}` value, or change the column's type with `change column`." # Object-not-found errors. Anchor: "no such ...". These are # genuinely single-line errors — no hint adds value. not_found: table: headline: "no such table: `{name}`" column: headline: "no such column: `{table}.{column}`" column_unqualified: headline: "no such column: `{column}`" relationship: headline: "no such relationship: `{name}`" # Name-collision errors. Anchor: "already exists". already_exists: table: headline: "table `{name}` already exists" column: headline: "column `{table}.{column}` already exists" relationship: headline: "relationship `{name}` already exists" # Generic catch-all when the translator can't classify the # engine error into a known category. The wording stays # engine-neutral; the message text from the engine is NOT # surfaced (ADR-0002 user-facing posture). generic: headline: "the database refused this `{operation}`." hint: "The operation could not be completed against the current state of `{table}`." # Used when no table is in context (e.g. contextless `friendly_message()` # callsites: replay, undo, rebuild, export) so the hint never leaks a # literal `{table}` placeholder. hint_no_table: "The operation could not be completed against the current database state." # Errors that are specifically about value validation # (DbError::InvalidValue) — wrong arity, wrong literal # form, etc. Pre-engine; the catalog covers what the # parser / validator already decided was wrong. invalid_value: arity: headline: "expected {expected} value(s), got {actual}." empty_insert: headline: "INSERT requires at least one column value." empty_update: headline: "UPDATE requires at least one assignment." # ---- Help text (CLI banner + in-app `help` command) ------------------ # ---- CLI argument-parsing errors (stderr before TUI starts) --------- cli: missing_value: "missing value for --{flag}" invalid_value: "invalid value for --{flag}: {value} (expected one of: {expected})" unknown_argument: "unknown argument: {arg}" multiple_paths: "only one project path may be supplied; got both `{first}` and `{second}`" resume_with_path: |- --resume and a positional are mutually exclusive; pass one or the other help: # CLI usage banner. Printed by `--help` / `-h` and on # argument-parse failure. Multi-line block; consumers # iterate lines or print as-is. cli_banner: | rdbms-playground — a TUI playground for relational database concepts Usage: rdbms-playground [options] [] Arguments: Path to an existing project directory. Without this, a fresh auto-named temp project is created in the data dir. Options: -h, --help Print this help and exit. --theme Override theme (default: auto-detect). --data-dir Use PATH as the data root instead of the OS-standard location for this run. --log-file Write tracing output to PATH. --resume Open the most-recently-used project (path tracked under /last_project). Errors out if no previous project is recorded. Mutually exclusive with . --no-undo Disable the undo machinery for this run: no snapshot is taken before each change (no per-command overhead), and undo/redo report that undo is turned off. --mode Start in this input mode, overriding the project's stored mode. Without it, the project's last-used mode is restored (default: simple). --demo Demonstration mode: show on-screen badges for otherwise-invisible keys (Tab, Enter, ...) — for screencasts and live teaching. App-level commands (typed inside the app, available in both modes): quit Exit cleanly. mode simple|advanced Switch input mode. help Show this list of commands in-app. save Save the current temp project under a chosen name (or `save as` to copy a named project to a new location). save as Always prompt for a target name/path. new Close current, create a fresh temp. load Open the project picker. rebuild Rebuild playground.db from project.yaml + data/, with confirmation. export [] Write a zip of project.yaml + data/ to (relative paths under the data root). Excludes playground.db and history.log. import [as ] Unpack into a new project and switch to it. overrides the target name (else taken from the zip). copy [all|last] Copy the output panel to the system clipboard (`copy last` copies just the most recent command's output). # In-app `help` command output (ADR-0024 §help_id). The # renderer iterates the command REGISTRY and translates each # CommandNode's `help_id` — so a newly-registered command # appears in `help` automatically, with no edit here. `intro` # and `dsl_section` frame the two command groups; each # `app.*` / `ddl.*` / `data.*` entry is keyed by a command's # `help_id`; `types_reference` closes the block. All entries # are multi-line-capable — the renderer emits one output row # per line so scroll math stays accurate. intro: "Supported commands:" dsl_section: "DSL data commands (in simple mode):" # H3: footer on the full `help` list, and the not-found note # for `help `. `{topic}` is the word the user typed. detail_hint: "Type `help ` for detail on one command (e.g. `help insert`), or `help types` for the type reference." unknown_topic: "No help for `{topic}`. Type `help` for the full command list, or `help types` for the type reference." # Per-command help, keyed by `CommandNode.help_id`. Block # scalars (`|-`) so the column alignment survives — the # double-quoted form trips a libyml scanner bug on long # internal space runs. The renderer emits one output row per # line, so multi-form commands list each form on its own line. app: quit: |- quit — exit the app help: |- help — show this command list help — detailed help for one command (e.g. `help insert`) hint: |- hint — explain the most recent error (press F1 for a hint on what you're typing) rebuild: |- rebuild — rebuild the project database from project.yaml + data/ (with confirmation) save: |- save — save the current temp project under a name save as — copy the current project to a new name/path new: |- new — close the current project, start a fresh temp project load: |- load — open the project picker export: |- export [] — write a zip of project.yaml + data/ (excludes the database file and history.log) import: |- import [as ] — unpack a zip into a new project and switch to it mode: |- mode simple|advanced — switch input mode messages: |- messages [short|verbose] — show or switch error-message verbosity (verbose is the default) undo: |- undo — undo the last change (with confirmation) redo: |- redo — redo the last undone change (with confirmation) copy: |- copy [all|last] — copy the output panel to the clipboard (`copy last` = the most recent command) ddl: create: |- create table with pk [(), ...] — create a table create_m2n: |- create m:n relationship from to [as ] — build a junction table linking two tables sql_create_table: |- create table [if not exists] ( [not null] [unique] [primary key] [default ] [check ()] [references

[()]], ... [, primary key (, ...)] [, unique (, ...)] [, check ()] [, [constraint ] foreign key () references

[()]]) — create a table (advanced SQL) sql_drop_table: |- drop table [if exists] — remove a table (advanced SQL) sql_create_index: |- create [unique] index [if not exists] [] on (, ...) — create an index (advanced SQL) sql_drop_index: |- drop index [if exists] — remove an index (advanced SQL) sql_alter_table: |- alter table add column [not null] [unique] [default …] [check …] alter table drop column alter table rename column to alter table rename to alter table alter column type alter table add [constraint ] check () | unique (, …) | foreign key () references

[()] alter table drop constraint — evolve a table's columns and constraints (advanced SQL) drop: |- drop table — remove a table drop column [from] [table] : [--cascade] — remove a column (--cascade also drops any index that covers the column) drop relationship — remove a relationship drop index — remove an index drop index on (, ...) — remove an index by its columns add: |- add column [to] [table] : () — add a column (for serial/shortid on a non-empty table: existing rows auto-filled) add 1:n relationship [as ] from

. to . [on delete ] [on update ] [--create-fk] — declare a relationship add index [as ] on (, ...) — create an index rename: |- rename column [in] [table] : to — rename a column change: |- change column [in] [table] : () [--force-conversion | --dont-convert] — change a column's type (to serial/shortid: null cells auto-filled with generated values) data: show: |- show table — show a table's structure show data — show a table's rows show tables — list all tables show relationships — list all relationships show indexes — list all indexes show relationship — show one relationship's detail show index — show one index's detail seed: |- seed [] — fill a table with generated sample rows (default 20). Existing rows are kept; foreign keys draw from existing parent rows. seed ... set = 'v' | in ('a','b') | as | between x and y — pin how a column is generated: a fixed value, a pick-list, a named generator (email, name, product, ...), or a range. seed . [set ...] — fill one column across the EXISTING rows (the follow-up to `add column`). seed ... --seed — reproducible: same data for the same n. insert: |- insert into [(cols)] [values] (vals) — add a row update: |- update set =, ... where = | --all-rows — change matching rows delete: |- delete from where = | --all-rows — remove matching rows replay: |- replay — run each non-blank, non-`#`-comment line of as a command. Stops at the first error (no rollback); relative paths resolve under the project directory. explain: |- explain show data | explain update ... | explain delete from ... — show how the database would run a query, without running it (safe even for update / delete) explain (advanced mode) — the same plan for the SQL you wrote # Type reference, appended after the command list. types_reference: | Types: text, int, real, decimal, bool, date, datetime, blob, serial, shortid Auto-generated types (serial, shortid): serial — integer that auto-fills with the next sequence value (MAX(col)+1) on insert. Outside a primary key it carries a UNIQUE contract. shortid — short base58 identifier auto-filled at insert time. Always carries a UNIQUE contract. Adding or changing-to either type on a non-empty table auto-fills existing/null cells in the same operation. # ---- DSL parse error rendering -------------------------------------- # ---- Hint panel ambient typing assistance (ADR-0022 §6) ------------- hint: # The hint panel goes ambient as soon as the user types # anything — empty input keeps the existing # `panel.hint_empty` content. # Hint sentences are full standalone phrases, capitalised # at the start. Inline `{message}` substitutions inherit # whatever case the source produced (parser errors, # engine messages) — they're embedded mid-sentence so they # stay lowercase by convention. ambient_complete: "Submit with Enter" ambient_expected: "Next: {expected}" ambient_error_with_usage: "{message} — usage: {usage}" # H2 / ADR-0053: shown by `hint` / F1 when there is nothing specific # to expand on (no recent error, empty input). getting_started: "Start typing a command and press F1 for a hint, or type `help` for the full command list." # ── Tier-3 teaching blocks (ADR-0053 D3) ────────────────────────── # Per-form command hints (`hint.cmd.

`) and per-class error # hints (`hint.err.`), each a `what` (1–2 sentences) / `example` # (one runnable, mode-correct line) / `concept` (the relational idea — # the teaching part). Phase B seeds the three approved exemplars; the # rest are authored in Phase C. cmd: insert: what: "Add one or more rows to a table." example: "insert into Customers values ('Ann', 'ann@example.io')" concept: "A row is one record; each value lines up with a column, in order. Columns typed `serial`/`shortid` fill themselves — leave them out." add_relationship: what: "Link two tables so a parent row can own many child rows." example: "add 1:n relationship from Customers.id to Orders.customer_id" concept: "The \"1:n\" means one parent, many children. The child column holds the foreign key; add `--create-fk` to create that column if it doesn't exist yet." # App-lifecycle commands (Phase C batch 1). Reference-leaning, so # `concept` appears only where there's a real idea to teach. quit: what: "Leave the playground. Your project is already saved to disk." example: "quit" help: what: "List every command, or show the detail for one." example: "help insert" concept: "`help` is the reference; press F1 while typing for a hint about the command you're building right now." hint: what: "Explain the most recent error — or, pressing F1 while typing, the command you're building." example: "hint" rebuild: what: "Rebuild the project database from its saved text files." example: "rebuild" concept: "The text files (project.yaml + the data folder) are the source of truth; the database is derived and can always be rebuilt from them." save: what: "Save the current project under a name; `save as` copies it to a new one." example: "save as my-shop" new: what: "Close the current project and start a fresh temporary one." example: "new" load: what: "Open the project picker to switch to a saved project." example: "load" export: what: "Write a shareable zip of the project — its text files only, never the database." example: "export my-shop.zip" concept: "The zip carries the schema and data as text, so anyone can rebuild the very same database from it." import: what: "Unpack a project zip into a new project and switch to it." example: "import my-shop.zip as shop-copy" mode: what: "Switch between simple mode (the guided teaching commands) and advanced mode (raw SQL)." example: "mode advanced" concept: "Simple mode uses keyword commands; advanced mode lets you write SQL directly. A leading `:` runs a single advanced command without switching modes." messages: what: "Show or set how much detail error messages give." example: "messages short" concept: "Verbose (the default) adds a fix-it hint under each error headline; short shows just the headline." undo: what: "Undo the most recent change, after a confirmation." example: "undo" concept: "Every data or schema change is snapshotted first, so you can step back; `redo` re-applies what you undid." redo: what: "Re-apply the most recently undone change." example: "redo" copy: what: "Copy the output panel to the clipboard — all of it, or just the last command's output." example: "copy last" err: foreign_key: child_side: what: "The value you gave for the child column doesn't match any parent row, so the foreign key has nothing to point at." example: "First insert the parent (insert into Customers …), then the child that references it." concept: "A foreign key is a promise that every child points at a real parent, so the parent must exist first. To allow orphans on delete instead, set the relationship's `on delete` to `set null` or `cascade`." # Invalid identifier in a schema slot (ADR-0022 stage 8e # + the user's #5). Voice mirrors ADR-0019's "no such # {kind}" wording for consistency with engine errors. ambient_invalid_ident: "No such {kind}: `{found}`" # User-invented-name slot (NewName per IdentSlot). The # probe-derived `{next}` is what comes after the name — # e.g. `(` after a new column name. Empty/unknown `next` # falls through to `ambient_typing_name` instead. ambient_typing_name: "Type a name" ambient_typing_name_then: "Type a name, then {next}" # Issue #4 — advanced-mode CREATE TABLE element slot. Surfaced # at `create table T (` so the column-name role is visible # alongside the table-level constraint keywords. create_table_element: "Type a column name, or a table-level constraint: `primary`, `unique`, `check`, `constraint`, `foreign`" # Issue #26: the `seed ▮` position. The optional row count is # a bare number with no Tab candidate, so it (and the `.column` # column-fill form) would be invisible next to the `set` / `--seed` # chips. Names every option so the most common next move (a count) is # discoverable. seed_count: "Optionally a row count, e.g. `50` (default 20); `.column` to fill one column on existing rows; `set` to pin a column; `--seed` to fix the RNG" # Value-literal slot — `insert ... values (`, `update ... set # col=`, `where col=`. Replaces the misleading "null true # false" keyword candidate list with format guidance for all # accepted literal forms. Used when the walker can't resolve a # column type (schemaless parse, missing table, unknown column). value_literal_slot: "Type a value: number, 'text', true/false, null (dates as 'YYYY-MM-DD', datetimes as 'YYYY-MM-DDTHH:MM:SS')" # Per-column-type value-slot hints (ADR-0024 §Phase D §typed-value-slots). # Fired when the walker resolved the column's user-facing type # at the current value slot; narrows the prose to the relevant # literal forms for that type. Falls back to # `value_literal_slot` when the type can't be resolved. value_slot_int: "Type an integer (e.g. 42, -7) or null" value_slot_real: "Type a number (e.g. 3.14, -0.5) or null" value_slot_decimal: "Type a number (e.g. 19.95, -2.50) or null" value_slot_bool: "Type true, false, or null" value_slot_text: "Type a quoted string (e.g. 'Alice') or null" value_slot_date: "Type a quoted date as 'YYYY-MM-DD' or null" value_slot_datetime: "Type a quoted datetime as 'YYYY-MM-DD HH:MM:SS' or null" value_slot_blob: "Type a quoted blob literal or null" # Serial / shortid in `values (…)` form: the user must enter # something at this position (no "skip column" syntax). `null` # is the auto-fill path (ADR-0018: serial / shortid columns # auto-fill null cells on insert), so the prose leads with # null and offers an explicit value as the alternative. value_slot_serial: "Type null to auto-generate, or an explicit integer" value_slot_shortid: "Type null to auto-generate, or a quoted shortid" # Wrapper that prefixes a per-column-type slot hint with the # actual column name so the user sees "for `Email`: Type a # quoted string …" instead of the generic type prose. value_slot_for_column: "for `{column}`: {detail}" # Pedagogical note appended at the first value slot of a # Form B `insert into T values (…)` when T has auto-generated # columns the value list skips — points the user at the # explicit-column form so the skipped column is discoverable. value_slot_autogen_skipped: "({columns} auto-generated — skipped here; list columns explicitly, e.g. `insert into T (...) values (...)`, to set it.)" # Advanced-mode SQL DDL execution notes (ADR-0035). ddl: # `create table if not exists ` where the table is already # present: a no-op that succeeds with this note instead of an # "already exists" error. create_skipped_exists: "table '{name}' already exists — skipped (no changes made)" # `drop table if exists ` where the table is absent: a no-op that # succeeds with this note instead of a "doesn't exist" error. drop_skipped_absent: "table '{name}' doesn't exist — skipped (no changes made)" # `create [unique] index if not exists …` where the index name # already exists: a no-op that succeeds with this note (ADR-0035 §4d). create_index_skipped_exists: "index '{name}' already exists — skipped (no changes made)" # `drop index if exists ` where the index is absent: a no-op that # succeeds with this note instead of a "doesn't exist" error. drop_index_skipped_absent: "index '{name}' doesn't exist — skipped (no changes made)" parse: # Wrapper around chumsky's structural error message. The # caret pointer (visualising the failure column) is printed # on its own preceding line via `parse.caret`. error: "parse error: {detail}" # Wrapper used by `ParseError::Display` (so any to_string() # call on a parse error renders consistently with the in-app # error rendering). error_wrapper: "could not parse command: {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 ()` to choose. Use a comma-separated list for compound primary keys. # ADR-0026 §1: the recursion-depth guard on the # WHERE-expression grammar. Input nested past the cap # (`((((…))))`) stops here with a friendly error instead # of overflowing the parser stack. expression_too_deep: "expression nested too deeply" on_action_specified_twice: "`on {target}` specified twice" change_column_flags_exclusive: "`--force-conversion` and `--dont-convert` are mutually exclusive — pick one." # ADR-0035 §4g: adding a primary key to an existing table is not # supported — every table is created with its primary key. alter_add_primary_key: "a table's primary key is fixed at creation — `alter table … add primary key` is not supported." # ADR-0035 §4g: composite UNIQUE constraints are unnamed in this # tool, so a `constraint ` prefix on UNIQUE has nowhere to go. alter_named_unique: "a UNIQUE constraint cannot be named — use `alter table add unique (, …)` without `constraint `." unknown_type: "unknown type '{found}' (expected one of: {expected})" unknown_action: "unknown referential action '{found}' (expected one of: {expected})" # Phase D typed-value-slot mismatch (ADR-0024 §Phase D): # surfaced when a column's value slot rejects the literal # the user typed (e.g. `3.14` at an `int` column). `{found}` # is the literal text; `{expected}` names the required # shape (`integer`, `number`, …). bind_type_mismatch: "value '{found}' is not a valid {expected}" # `insert into T (col)` with no `values (...)` afterwards # — the shared `(…)` opener matched as a Form-A column # list but the user hasn't typed the value clause yet. # Surfaced as a parse-time error so the input renderer # classifies the input as mid-typing rather than # dispatching a logically-empty Form C insert. insert_form_a_missing_values: "`insert into ...({columns})` looks like Form A — add `values (...)` to supply the matching values." # ADR-0029 §9: a primary-key column is already NOT NULL, # and a single-column primary key is already UNIQUE — # declaring either explicitly is redundant. constraint_redundant_on_pk: "`{column}` is a primary-key column, so it is already {constraint} — drop the redundant constraint." # Caret pointer showing where in the input the parser # failed. `{padding}` is the leading whitespace; the # template appends `^` so the rendered line places the # marker under the offending character. caret: "{padding}^" # Default for the `ParseError::Empty` variant — surfaces as # `{detail}` inside the wrapper. empty: "empty input" # No-prefix fallback (ADR-0021 §5): when the parse fails # before any keyword is consumed, the renderer lists every # command-entry keyword instead of attempting a per-command # usage block. `{commands}` is an oxford-joined list of # command-keyword renderings (each from # `parse.token.keyword.*`). available_commands: "available commands: {commands}" # ADR-0042 G2: collapse the SELECT projection-start expression # first-set (14 expression-starters plus the `distinct`/`all` # quantifiers) into one learner-sized gloss in the error # message. The detector keys on `distinct` AND `all` being # jointly expectable, which only happens at a projection start — # so the raw set is replaced *in the error line only*; # completion/hints still expand the full first-set. expect: select_projection: "a projection: `*`, a column, or an expression" # ADR-0042 §3: a CROSS JOIN pairs every row and takes no ON # clause. The grammar rejects a following `on`; this message # (rendered in place of the generic structural error when the # most recent join is a CROSS join and the failing token is `on`) # teaches the distinction instead of just "expected end of input". cross_join_no_on: "a CROSS JOIN has no ON clause — it pairs every row; for a join condition use `JOIN … ON`, or filter with `WHERE`" # Per-command usage templates (ADR-0021 §1). Rendered under a # "usage:" prefix when a parse fails after consuming a # known command-entry keyword. The bracket convention `[...]` # marks optional parts; angle-bracket `<...>` marks # placeholders. ADR-0009's surface conventions apply. usage: create_table: "create table with pk [()[, ...]]" create_m2n: "create m:n relationship from to [as ]" # Terse one-line synopsis (issue #12): the full grammar — every # column- and table-level constraint — lives in `help.ddl.sql_create_table`. sql_create_table: "create table [if not exists] ( [constraints], ...)" sql_drop_table: "drop table [if exists] " sql_create_index: "create [unique] index [if not exists] [] on
([, ...])" sql_drop_index: "drop index [if exists] " sql_alter_table: |- alter table
add column [not null] [unique] [default ] [check ()] alter table
drop column alter table
rename column to alter table
rename to alter table
alter column type alter table
add [constraint ] check () | unique (, ...) | foreign key () references [()] alter table
drop constraint drop_table: "drop table " drop_column: "drop column [from] [table]
: " drop_relationship: |- drop relationship drop relationship from .to .drop_index: |- drop index drop index on
([, ...]) drop_constraint: "drop constraint (not null | unique | default | check) from
." add_column: "add column [to] [table]
: ()" add_relationship: |- add 1:n relationship [as ] from .to .[on delete ] [on update ] [--create-fk] add_index: "add index [as ] on
([, ...])" add_constraint: |- add constraint not null to
.add constraint unique to
.add constraint default to
.add constraint check () to
.rename_column: "rename column [in] [table]
: to " change_column: |- change column [in] [table]
: () [--force-conversion | --dont-convert] seed: "seed
[count] [set = ... | in (...) | as | between x and y] | seed
." show_data: "show data
" show_table: "show table
" show_tables: "show tables" show_relationships: "show relationships" show_indexes: "show indexes" show_relationship: "show relationship " show_index: "show index " insert: "insert into
[([, ...])] [values] ([, ...])" update: "update
set =[, ...] (where = | --all-rows)" delete: "delete from
(where = | --all-rows)" explain: |- explain show data
[where ] [limit ] explain update
set =[, ...] (where | --all-rows) explain delete from
(where | --all-rows) explain (advanced mode) replay: "replay | replay ''" # SQL `SELECT` (advanced mode; ADR-0030 / ADR-0031). select: "select (* | [ as ][, ...]) from
[where ] [order by [ asc|desc][, ...]] [limit ]" # SQL `WITH` / CTE (advanced mode; ADR-0032). G4 (ADR-0042): # its own template — `with` previously borrowed `select`'s, # which never showed the CTE shape. with: "with [recursive] [([, ...])] as ()[, ...] select ..." # 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" help: "help []" hint: "hint" rebuild: "rebuild" save: "save | save as" new: "new" load: "load" export: "export []" import: "import [as ]" mode: "mode simple | mode advanced" messages: "messages | messages short | messages verbose" undo: "undo" redo: "redo" copy: "copy | copy all | copy last" # ---- Pre-submit diagnostics (ADR-0027) ------------------------------- # Surfaced by the validity indicator and the hint panel before # a command is submitted, so a learner sees the problem without # having to run the command and read the engine's error. diagnostic: unknown_table: "no such table: `{name}`" unknown_column: "no such column `{name}` on table `{table}`" # WARNING-severity flags (ADR-0026 §7) — the command still # runs, but the comparison is very likely not intended. type_mismatch: "`{column}` is {type} — this comparison uses a value of a different type" eq_null: "`= NULL` is never true — use `IS NULL` or `IS NOT NULL`" # ADR-0027 Amendment 1: LIKE is a text-pattern match. like_numeric: "`{column}` is {type} — `LIKE` is a text-pattern match, not a {type} comparison" # Phase-2 diagnostic keys (ADR-0032 §11.5). unknown_qualifier: "no such table or alias in scope: `{qualifier}`" ambiguous_column: "`{column}` is ambiguous — appears in {qualifiers}" projection_alias_misplaced: "alias `{alias}` cannot be used in {clause} — aliases are not bound until after `SELECT`'s projection list" # Issue #31: a bare table alias / table name used where the grammar # expects a column (e.g. `GROUP BY o`). The name *is* in scope — it # is the alias of a FROM source — so calling it an "unknown column" # misleads. Point the learner at the qualified `alias.column` form. alias_used_as_column: "`{name}` is a table alias — write `{name}.` to reference one of its columns" table_used_as_column: "`{name}` is a table — write `{name}.` to reference one of its columns" cte_arity_mismatch: "CTE `{cte}` declares {declared} columns but its body has {actual}" compound_arity_mismatch: "`{op}` requires both sides to have the same number of columns — left has {left_n}, right has {right_n}" duplicate_cte: "duplicate `WITH` table name: `{name}`" # ADR-0033 §8 — Phase-3 DML diagnostic keys. auto_column_overridden: "column `{column}` is auto-generated (`{type}`); providing an explicit value bypasses the auto-counter and may collide with later auto-generated values" insert_arity_mismatch: "the column list names {expected} column(s) but {actual} value(s) are given" # ADR-0033 §8.1 / Amendment 5: Form B (no column list) variant # (advanced mode — auto-fills nothing, so every column needs a value). insert_arity_mismatch_form_b: "with no column list, all {expected} column(s) need a value but {actual} value(s) are given" # ADR-0036 Amendment 1 / issue #17: simple-mode Form B. The DSL # auto-fills serial/shortid columns, so only the user-fillable columns # take values — name both sets so the learner understands the skip. insert_arity_mismatch_form_b_simple: "without a column list, supply {expected} value(s) for {columns} — {skipped} auto-generated; {actual} given" # ADR-0036 Amendment 1 / issue #17: simple-mode Form B where every # column is auto-generated, so the values list takes nothing. insert_arity_mismatch_all_auto: "every column of `{table}` is auto-generated, so no values are needed, but {actual} value(s) are given" not_null_missing: "column `{column}` is required (`NOT NULL`, no default); the statement will fail when run" # Engine-error translations: an engine-rejected SQL statement # reaches the friendly-error layer (ADR-0019) and these keys # render its message in engine-neutral wording (ADR-0030 §7). # The keys are reached only after a parse-time pass let the # query through and the engine rejected at execution. engine: no_such_table: "no such table: `{name}`" no_such_column: "no such column: `{name}`" ambiguous_column: "`{column}` is ambiguous — qualify it with a table or alias" aggregate_misuse: "`{name}()` cannot be used here — aggregates belong in projection or `HAVING`, not `WHERE`" group_by_required: "non-aggregated columns must appear in a `GROUP BY` clause or be wrapped in an aggregate function" compound_arity_mismatch: "`{op}` requires both sides to have the same number of columns" scalar_subquery_too_many_rows: "scalar subquery returned more than one row — use `IN (…)` or limit the inner query" recursive_cte_malformed: "recursive CTE shape is invalid — needs a non-recursive base case combined with `UNION`/`UNION ALL`" # ---- Project lifecycle event notes ----------------------------------- project: rebuild_ok: "[ok] rebuild — {summary}" rebuild_failed: "rebuild failed: {error}" switched_ok: "[ok] now editing: {display_name}" switch_failed: "project switch failed: {error}" export_ok: "[ok] export — wrote {path}" export_failed: "export failed: {error}" # Usage / argument-parsing errors for app-level commands. export_usage: "usage: export []" import_usage: "usage: import [as ]" import_empty_target: "import: empty target after `as`" # Project-switch validation failures (load / save-as / # import). Returned from the runtime as Err(String) and # surfaced via project.switch_failed. 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 # the calling code itself. resume_recorded_missing: "--resume: recorded project `{path}` no longer exists" resume_no_previous: "--resume: no previous project recorded under `{data_root}`" # Project-lock errors (single-instance enforcement, ADR-0015 §6). lock: already_held: |- project is already open in another rdbms-playground process (pid {pid} on host `{hostname}`); close that process or remove `{path}` if you're sure it's not running write: "could not write lock file `{path}`: {source}" read: "could not read existing lock file `{path}`: {source}" # Temp-project name generation failures. naming: wordlist_too_small: "wordlist must contain at least 3 entries; found {count}" too_many_collisions: |- could not generate a non-colliding temp project name after {attempts} attempts # User-typed project name validation failures. user_name: empty: "project name cannot be empty" leading_dot: "project name cannot start with `.`" invalid_char: "project name cannot contain `{ch}`; use letters, digits, `-`, `_`, or `.` only" # ProjectError variants (ProjectError Display path). data_root_unavailable: |- could not determine the OS-standard data directory; pass --data-dir to override path_not_found: "project path `{path}` does not exist" not_a_project: |- path `{path}` does not look like a project directory (no `project.yaml` and no `playground.db`) already_exists: "path `{path}` already exists; pick a different name or remove it first" io: "filesystem error at `{path}`: {source}" # SafeDeleteError — surfaces in logs when temp-project cleanup # refuses to delete a path (ADR-0015 §13). safe_delete: refused: "refusing to delete `{path}`: {reason}" io: "io error on `{path}`: {source}" # ---- DSL failure wrapper + advanced-mode placeholder + fatal -------- dsl: # The friendly-error layer's translated reason, shown beneath the # failed command's echo line. ADR-0040: the redundant # `" " failed:` prefix was dropped — the echo line # carries the ✗ marker and names the command. failed: "{rendered}" # Echo line `running: ` shown above each command's # response while it executes; ADR-0040 resolves it to # ` ✓` / ` ✗` on completion (the marker replaces # the old `[ok] ` summary line). running: "running: {input}" # ---- Value-validation errors (per-column at bind time) -------------- value: type_mismatch: "column `{column}` expects {expected_human}, got {got}" format: "column `{column}`: {message}" # ---- Archive / zip errors (export / import) ------------------------- archive: io: "io error on `{path}`: {source}" zip: "zip error on `{path}`: {message}" export_sequence_exhausted: |- could not pick an export filename for `{project}` in `{target_dir}`: all sequence numbers up to {limit} are taken import_collision_exhausted: |- destination `{path}` already exists and the auto-suffix retries (-02 through -{limit}) are also taken; use `import as ` to choose a different name invalid_zip: "zip is malformed: {detail}" not_a_project_archive: |- zip does not contain a project (no `project.yaml` under a single top-level folder) multiple_top_folders: "zip contains more than one top-level folder; refusing to extract" unsafe_entry: "zip entry `{entry}` would escape the target directory; refusing to extract" # ---- Persistence-layer errors (CSV/YAML/IO) ------------------------- # These were thiserror Display attributes pre-round-6. Most surface # only as the inner `{message}` of `fatal.persistence` or as the # wrapped detail inside `DbError::PersistenceFatal`. persistence: io: "could not {operation} `{path}`: {source}" encode: "could not encode {kind} for `{path}`: {message}" csv: empty: "CSV is empty" invalid_utf8: "invalid UTF-8 in CSV body" unterminated_quote: "unterminated quoted field" yaml: syntax: "project.yaml syntax error: {detail}" unsupported_version: "unsupported project.yaml version: {version} (expected 1)" unknown_type: "unknown user-facing column type `{raw}` for `{table}.{column}`" unknown_action: "unknown referential action `{raw}`" migrate: version_parse: "could not read version field from project.yaml: {detail}" newer_than_supported: |- project.yaml is at version {file} but this build only understands up to version {latest}; upgrade the application or restore an older project.yaml no_migrator: |- no migrator registered for version {version} (programmer error: registry latest_version disagrees with migrators length) step_failed: "migrator from v{from} to v{to} failed: {source}" bad_output: "migrator produced an unparseable result: {detail}" io: "io error during migration on `{path}`: {source}" # ---- Advanced-mode SQL surface (ADR-0030) --------------------------- advanced_mode: not_implemented: "advanced mode SQL not implemented yet — echo: {input}" sql_in_simple: "`{command}` is SQL — available in advanced mode. Switch with `mode advanced`, or prefix the line with `:` to run it once." # Appended to a simple-mode DSL error when the same line would run # in advanced mode — keeps the actionable DSL fix and adds the mode # pointer (ADR-0033 Amendment 3). Suppressed by `advanced_alternative_note` # for cases the advanced engine would also reject (issue #1). also_valid_sql: "(trying to write SQL? switch with `mode advanced`, or prefix `:` to run once)" # ---- Insert education hints (issue #1 sub-task 2) ------------------- # Appended to a simple-mode INSERT Form B parse error when the user # supplied more values than Form B's non-auto-generated columns expect # (but not more than every column). Teaches the contract that excludes # `serial` / `shortid` columns and points at the column-list form. insert: form_b_extra_values_note: "`insert into {table} values (…)` expects {expected_phrase} — {auto_phrase}. To set them explicitly, list every column: `insert into {table} ({all_cols}) values (…)`." # Pre-flight teaching note for advanced-mode positional # `INSERT INTO T VALUES (…)` (no column list) when the value count # doesn't match the column count (issue #1 sub-task 3). The override # uses the non-auto columns so the user can omit serial/shortid. form_b_positional_count_mismatch_note: "Positional `insert into {table} values (…)` requires a value for every column. `{table}` has {col_count} columns ({col_list}); you supplied {supplied}. To omit auto-generated columns, list the columns you want: `insert into {table} ({non_auto_csv}) values (…)`." # ---- SQL SELECT (advanced mode; ADR-0030 / ADR-0031) ---------------- select: internal_table: "`{table}` is an internal system table and cannot be queried." # ---- Persistence-fatal banner (ADR-0015 §8) ------------------------- fatal: persistence: "FATAL: failed to {operation} `{path}` — {message}. Quitting; investigate and restart." # ---- Modal labels (load picker, rebuild confirm, save-as path) ------ modal: rebuild_cancelled: "rebuild cancelled" load_cancelled: "load cancelled" generic_cancelled: "{title} cancelled" path_entry_empty_name: "path entry: empty name" path_entry_empty_path: "path entry: empty path" load_picker_nothing: "nothing to load" # Modal titles + body prose rendered by ui.rs. load_picker_title: "Load project" load_picker_empty: "(no projects in data directory)" load_picker_path_prompt: "Path to project directory:" rebuild_confirm_title: "Rebuild project" rebuild_confirm_prompt: "Continue?" # Undo / redo confirmation (ADR-0006 Amendment 1). undo_confirm_title: "Undo last change" redo_confirm_title: "Redo last undone change" undo_confirm_command: "This will undo:" redo_confirm_command: "This will re-apply:" undo_confirm_when: "Snapshot taken {timestamp}" undo_confirm_prompt: "Restore that earlier state?" undo_cancelled: "undo cancelled" redo_cancelled: "redo cancelled" # ---- Undo / redo command surfaces (ADR-0006 Amendment 1) ------------- undo: disabled: "undo is turned off for this session (started with --no-undo)" nothing_to_undo: "nothing to undo" nothing_to_redo: "nothing to redo" undone_ok: "undone: {command}" redone_ok: "re-applied: {command}" undo_failed: "could not undo: {error}" redo_failed: "could not redo: {error}" # ---- Save / save-as command surfaces --------------------------------- save: # `save` on a named project is a no-op with a friendly hint. already_saved: "already auto-saved; use `save as` to copy to a different location" # Modal titles for `save` (on a temp) vs `save as`. title_as: "Save as" title_save: "Save" # Prompt body for the path-entry modal opened by save / save as. path_prompt: "Name (under data dir/projects) or absolute path:" # ---- Status bar (project label) and panels --------------------------- status: no_project: "(no project)" project_label: "Project: " panel: tables_title: "Tables" tables_empty: "(none yet)" relationships_title: "Relationships" relationships_empty: "(none)" hint_empty: "Type a command — press Tab for options, `help` for a list" # Mode-discovery pointer appended to the empty-input hint in SIMPLE # mode (ADR-0051): the `mode advanced` switch left the keybinding # strip, so the hint advertises it. Leading separator continues the # prompt line. Advanced mode shows no pointer — users know how they # got there, and `help` covers the way back. hint_mode_advanced: " · `mode advanced` for SQL" # 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) ------- # The bottom strip is keystrokes-only and state-aware (ADR-0051). Labels # pair with a key name in the renderer (e.g. `Enter` + `run`). shortcut: confirm: "confirm" cancel: "cancel" yes: "Yes" no: "No" load: "load" select: "select" browse_path: "browse path" back_to_list: "back to list" # Status-strip labels (ADR-0051, issue #27). run: "run" nav: "sidebar" next_pane: "next pane" scroll: "scroll" to_input: "input" cycle: "cycle" browse: "browse" clear: "clear" complete: "complete" history: "history" home_end: "home/end" del_word: "del word" # ---- mode / messages banners (app-level commands) ------------------- mode: set_simple: "mode: simple" set_advanced: "mode: advanced" show_simple: "mode: simple" 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}" set_short: "messages: short" set_verbose: "messages: verbose" unknown: "unknown messages mode '{value}' (expected 'short' or 'verbose')" # ---- copy (app-level command, ADR-0041) ----------------------------- copy: done: "copied {count} line(s) to the clipboard" nothing: "nothing to copy — the output panel is empty" unknown: "unknown copy target '{value}' (expected 'all' or 'last')" # ---- Database-error fallback wording + cascade summaries ------------ db: # DbError variants — fallback Display wording for paths that # bypass the structured friendly translator (fatal banners, # plain to_string() calls). The normal path goes through # `friendly::translate_error` which routes by the `kind` # field and renders catalog wording from `error.*` instead. error: sqlite: "database error: {message}" unsupported: "operation not supported: {detail}" invalid_value: "invalid value: {detail}" persistence_fatal: "could not {operation} `{path}`: {message}" rebuild_row_failed: |- unable to load row {row_number} from `{csv_path}` into table `{table}`: {detail} worker_gone: "database worker is no longer available" io: "io error: {detail}" 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`." # `add constraint ...` dry-run refusals (ADR-0029 §5). # Surface when the column's existing rows would violate the # constraint being added; the offending rows follow in a # diagnostic table. There is no force override — the user # fixes the data and retries. add_not_null_summary: |- Cannot add NOT NULL to `{table}.{column}`: {total} row(s) hold a null value. add_unique_summary: |- Cannot add UNIQUE to `{table}.{column}`: {total} value(s) appear in more than one row. add_check_summary: |- Cannot add this CHECK to `{table}.{column}`: {total} row(s) do not satisfy `{rule}`. # ---- DSL command success summaries (ADR-0019 §9 sweep) -------------- # (The DSL → SQL teaching echo's `Executing SQL:` prefix used to live # here as `echo.executing_sql`; with the ADR-0038 §4 styled-runs polish # the line is now built from a hardcoded constant in `crate::echo` # because the dim-prefix + lex-the-rest rendering path in # `ui::render_output_line` needs a fixed byte boundary the i18n # template couldn't provide. Re-introduce a key here if a non-English # locale lands.) # Seed-command notes (ADR-0048): the cap note when the unique-value # space is exhausted, and the advisory that flags columns filled with # generic text that look like fixed value sets. seed: capped: "(of {requested} requested — ran out of distinct value combinations)" # ADR-0048 D13 (Phase 2/3 wording): name the generically-filled # enum-ish / CHECK columns and point at the concrete repairs — the # `set` clause on a fresh seed, or the column-fill form for the rows # just created. advisory_generic: "{columns} filled with generic text — they look like fixed value sets. Pin them next time with `set {column} in ('…', '…')`, or fix these rows with `seed {table}.{column} set {column} in ('…', '…')`." ok: # ADR-0040: the generic `[ok] ` summary line was # retired — a successful command's echo line now carries a ✓ # marker (the verb+subject duplicated the echo above it). The # per-operation row-count footers below still convey real payload # and are unchanged. rows_inserted: " {count} row(s) inserted" rows_seeded: " {count} row(s) seeded into {table}" rows_updated: " {count} row(s) updated" rows_deleted: " {count} row(s) deleted" # Shown beneath a `drop column --cascade` summary, once per # index removed because it covered the dropped column. index_dropped_with_column: " also dropped index `{index}` (it covered the column)" # ---- Client-side success notes (ADR-0017 §6, ADR-0018 §9) ------------ client_side: # Per-cell transformation notice when `change column ...` rewrote # at least one stored value (storage-class change or non-identity # mapping). `lossy` variant fires when information was discarded — # under simple-mode `--force-conversion`, and under advanced-mode # `alter table … alter column … type …`, which always converts # (ADR-0035 §7). transformed: |- [client-side] {count} row(s) were transformed before being stored. In raw SQL this would need an explicit `CAST` or application-level code. transformed_lossy: |- [client-side] {count} row(s) transformed; {lossy} of those discarded information (lossy). In raw SQL this would need an explicit `CAST` or application-level code. # Auto-fill notice when null cells were populated by the # serial/shortid auto-generation contract (change column path). auto_fill_transition: |- [client-side] {count} null cell(s) given auto-generated {kind} values. In raw SQL this would need an explicit UPDATE to populate. # Auto-fill notice for `add column T: x (serial)` on a # non-empty table — values run 1..N. auto_fill_add_serial: |- [client-side] {count} row(s) given auto-generated serial values 1..{count}. In raw SQL this would need an explicit UPDATE to populate. # Auto-fill notice for `add column T: x (shortid)` on a # non-empty table. auto_fill_add_shortid: |- [client-side] {count} row(s) given auto-generated shortid values. In raw SQL this would need an explicit UPDATE to populate. # `change column … --dont-convert` caveat (ADR-0038 §6 category 3, # Phase 3). The only category-3 *caveat* — the others are illuminating # (the headline echo already matches the effect). Here the headline # SQL (`ALTER TABLE … SET DATA TYPE …`) *would* convert if run, but # `--dont-convert` skipped the client-side layer, so the line above is # not equivalent. Fires only in an advanced effective mode (the line # references "the line above," i.e. the echo). dont_convert_caveat: |- [client-side] `--dont-convert` kept the stored values as-is; standard SQL always converts, so running the line above would transform them instead. # ---- Replay command surfaces (ADR-0019 §9 sweep) --------------------- replay: # Success summary printed when `replay ` completes # without per-line failure. completed: "[ok] replay {path} — {count} command(s) run" # File-open failure (line_number == 0 from the runtime). failed_open: "replay {path} failed: {error}" # Per-line failure header. The command echo is on a # follow-up `command_echo` line so the renderer can format # them as separate output rows. failed_at_line: "replay {path} failed at line {line_number}: {error}" # Indented echo of the offending command line, shown after # `failed_at_line` when the runtime supplied the source. command_echo: " > {command}" # Errors the runtime constructs inside ReplayFailed.error # before forwarding to App. Carried as plain text so they # compose with `failed_at_line`'s `{error}` placeholder. error_could_not_open: "could not open `{path}`: {detail}" error_parse: "parse error: {detail}" # Skipped during replay (ADR-0034): app-lifecycle commands are # not re-applied. Most skip silently; `import` and a nested # `replay` warn because skipping them can leave the replayed # state incomplete (imported data / the nested file's commands # are not reconstructed). skipped_import: "[skip] line {line}: `{command}` — replay does not re-import; the imported data is not reconstructed" skipped_replay: "[skip] line {line}: nested `{command}` — its commands were not replayed"