Files
rdbms-playground/src/friendly/strings/en-US.yaml
T
claude@clouddev1 329adfc935 fix(hint): labelled tier-3 block format + snapshot (ADR-0053 /runda)
Final /runda found the rendered block was three bare unlabelled lines,
deviating from the approved exemplar format. Fix:
- emit_tier3_block now renders a `Hint` heading + aligned `What:` /
  `Example:` / `Concept:` lines (hint.block.* labels); concept stays muted
- lock the format with an insta snapshot (hint_block_insert)
- amend ADR-0053 D2/D4 + exemplars: drop the `Next:` line (tier-2 ambient
  already owns live position-awareness — user-confirmed), align exemplars
  to the shipped format

2499 pass / 1 ignored, clippy clean.
2026-06-15 16:45:47 +00:00

1363 lines
76 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 <project-path> 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] [<project-path>]
Arguments:
<project-path> 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 <light|dark> Override theme (default: auto-detect).
--data-dir <PATH> Use PATH as the data root instead of
the OS-standard location for this run.
--log-file <PATH> Write tracing output to PATH.
--resume Open the most-recently-used project
(path tracked under <data-root>/last_project).
Errors out if no previous project is
recorded. Mutually exclusive with
<project-path>.
--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 <simple|advanced>
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 [<path>] Write a zip of project.yaml + data/ to
<path> (relative paths under the data
root). Excludes playground.db and
history.log.
import <zip> [as <t>] Unpack <zip> into a new project and
switch to it. <t> 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>`. `{topic}` is the word the user typed.
detail_hint: "Type `help <command>` 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 <command> — 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 [<path>] — write a zip of project.yaml + data/ (excludes the database file and history.log)
import: |-
import <zip> [as <target>] — 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 <T> with pk [<col>(<type>), ...] — create a table
create_m2n: |-
create m:n relationship from <T1> to <T2> [as <name>]
— build a junction table linking two tables
sql_create_table: |-
create table [if not exists] <T> (
<col> <type> [not null] [unique] [primary key] [default <expr>] [check (<expr>)] [references <P>[(<col>)]], ...
[, primary key (<col>, ...)] [, unique (<col>, ...)] [, check (<expr>)]
[, [constraint <name>] foreign key (<col>) references <P>[(<col>)]])
— create a table (advanced SQL)
sql_drop_table: |-
drop table [if exists] <T> — remove a table (advanced SQL)
sql_create_index: |-
create [unique] index [if not exists] [<name>] on <T> (<col>, ...)
— create an index (advanced SQL)
sql_drop_index: |-
drop index [if exists] <name> — remove an index (advanced SQL)
sql_alter_table: |-
alter table <T> add column <col> <type> [not null] [unique] [default …] [check …]
alter table <T> drop column <col>
alter table <T> rename column <old> to <new>
alter table <T> rename to <new>
alter table <T> alter column <col> type <type>
alter table <T> add [constraint <name>] check (<expr>) | unique (<col>, …) | foreign key (<col>) references <P>[(<col>)]
alter table <T> drop constraint <name> — evolve a table's columns and constraints (advanced SQL)
drop: |-
drop table <T> — remove a table
drop column [from] [table] <T>: <col> [--cascade] — remove a column
(--cascade also drops any index that covers the column)
drop relationship <name> — remove a relationship
drop index <name> — remove an index
drop index on <T> (<col>, ...) — remove an index by its columns
add: |-
add column [to] [table] <T>: <col> (<type>) — add a column
(for serial/shortid on a non-empty table: existing rows auto-filled)
add 1:n relationship [as <name>] from <P>.<col> to <C>.<col>
[on delete <action>] [on update <action>] [--create-fk] — declare a relationship
add index [as <name>] on <T> (<col>, ...) — create an index
rename: |-
rename column [in] [table] <T>: <old> to <new> — rename a column
change: |-
change column [in] [table] <T>: <col> (<newtype>) [--force-conversion | --dont-convert]
— change a column's type (to serial/shortid: null cells auto-filled with generated values)
data:
show: |-
show table <T> — show a table's structure
show data <T> — show a table's rows
show tables — list all tables
show relationships — list all relationships
show indexes — list all indexes
show relationship <name> — show one relationship's detail
show index <name> — show one index's detail
seed: |-
seed <T> [<count>] — fill a table with generated sample rows
(default 20). Existing rows are kept;
foreign keys draw from existing parent rows.
seed <T> ... set <c> = 'v' | in ('a','b') | as <gen> | 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 <T>.<col> [set ...] — fill one column across the EXISTING rows
(the follow-up to `add column`).
seed <T> ... --seed <n> — reproducible: same data for the same n.
insert: |-
insert into <T> [(cols)] [values] (vals) — add a row
update: |-
update <T> set <c>=<v>, ... where <c>=<v> | --all-rows — change matching rows
delete: |-
delete from <T> where <c>=<v> | --all-rows — remove matching rows
replay: |-
replay <path> — run each non-blank, non-`#`-comment line of <path>
as a command. Stops at the first error (no rollback);
relative paths resolve under the project directory.
explain: |-
explain show data <T> | explain update <T> ... | explain delete from <T> ...
— show how the database would run a query, without
running it (safe even for update / delete)
explain <select|with|insert|update|delete …> (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 block scaffolding (ADR-0053 D4): the heading + the labels the
# `what` / `example` / `concept` parts render under.
block:
heading: "Hint"
what: "What"
example: "Example"
concept: "Concept"
# ── Tier-3 teaching blocks (ADR-0053 D3) ──────────────────────────
# Per-form command hints (`hint.cmd.<form>`) and per-class error
# hints (`hint.err.<class>`), each a `what` (12 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"
# DDL — schema-shaping commands (Phase C batch 2).
create_table:
what: "Create a new table — its columns, their types, and a primary key."
example: "create table Customers with pk id(serial), name(text), email(text)"
concept: "A table is a set of rows that share the same columns. The primary key uniquely identifies each row; a `serial` key numbers the rows for you."
create_m2n:
what: "Create a junction table linking two tables many-to-many."
example: "create m:n relationship from Students to Courses"
concept: "A many-to-many link (a student takes many courses; a course has many students) can't live in either table, so it gets its own junction table holding a foreign key to each side."
add_column:
what: "Add a new column to an existing table."
example: "add column Customers: phone (text)"
concept: "Existing rows take the column's default, or null. A `not null` column with no default can't be added to a table that already has rows — there'd be nothing to put in them."
add_index:
what: "Create an index on one or more columns to speed up lookups."
example: "add index as idx_email on Customers (email)"
concept: "An index is a sorted side-structure that makes a lookup like `where email = …` fast, at the cost of a little space and slightly slower writes."
add_constraint:
what: "Add a constraint — not null, unique, default, or check — to an existing column."
example: "add constraint not null to Customers.email"
concept: "A constraint is a rule the database enforces on every row. Adding one fails if existing rows already break it, so you fix the data first."
drop_table:
what: "Remove a table and all of its rows."
example: "drop table Customers"
concept: "If other tables reference this one through a relationship, drop those relationships (or their child rows) first — the database won't orphan them."
drop_column:
what: "Remove a column from a table."
example: "drop column Customers: phone"
concept: "The column's values are lost. You can't drop a primary-key column, or one a relationship depends on."
drop_relationship:
what: "Remove a relationship between two tables."
example: "drop relationship customer_orders"
concept: "This drops the foreign-key link and stops the database enforcing it; the tables and their rows stay. The foreign-key column itself remains unless you also drop it."
drop_index:
what: "Remove an index by name."
example: "drop index idx_email"
concept: "Only the lookup shortcut goes — the data is untouched. Queries still work, just without that speed-up."
drop_constraint:
what: "Remove a constraint from a column."
example: "drop constraint not null from Customers.email"
concept: "The rule stops being enforced from now on; rows already stored are left as they are."
rename_column:
what: "Rename a column, keeping its values and type."
example: "rename column Customers: email to contact_email"
concept: "Only the name changes — the stored data is the same. References to the column are reconciled so nothing breaks."
change_column:
what: "Change a column's type, converting the existing values."
example: "change column Customers: status (int)"
concept: "The database converts each stored value to the new type; if a value can't convert it refuses the change, so you don't silently lose data. Flags let you force or skip the conversion."
# DML — querying and changing data (Phase C batch 3).
update:
what: "Change values in the rows that match a condition."
example: "update Customers set email = 'new@example.io' where id = 1"
concept: "The `where` clause picks which rows change, and it's required — pass `--all-rows` to change the whole table on purpose — so you never update more than you meant to."
delete:
what: "Remove the rows that match a condition."
example: "delete from Orders where status = 'cancelled'"
concept: "A `where` is required (use `--all-rows` to clear the table on purpose). Rows a relationship points at may be blocked or cascade-deleted, per its `on delete` action."
show_data:
what: "Show the rows stored in a table."
example: "show data Customers"
concept: "This reads the data and never changes it. Add a `where` to show only matching rows."
show_table:
what: "Show a table's structure — its columns, types, keys, and relationships."
example: "show table Customers"
concept: "Structure, not data: the column definitions and how this table links to others. Use `show data` to see the rows themselves."
show_tables:
what: "List all the tables in the project."
example: "show tables"
show_relationships:
what: "List all the relationships between tables."
example: "show relationships"
concept: "Each relationship is a foreign-key link from a child column to a parent's key, with an `on delete` / `on update` rule."
show_indexes:
what: "List all the indexes in the project."
example: "show indexes"
concept: "Indexes speed up lookups; this shows which columns each one covers and whether it enforces uniqueness."
seed:
what: "Fill a table with generated sample rows, or fill one column on existing rows."
example: "seed Customers 50"
concept: "Seeding invents realistic-looking data so you have something to query. Pin a value with `set col = …`, choose a generator with `as`, or give a numeric range with `between`."
explain:
what: "Show how the database will run a query — without running it."
example: "explain show data Customers where email = 'a@example.io'"
concept: "The plan reveals whether the database scans the whole table or jumps straight to rows through an index — the payoff of `add index`. `explain` never executes, so it's safe even on a delete."
replay:
what: "Re-run the commands recorded in a history file."
example: "replay session.log"
concept: "Every successful command is journalled, so replaying re-applies them in order to reproduce a project's state — handy for scripting or redoing a sequence."
# Advanced-mode SQL forms (Phase C batch 4). Examples are SQL, the
# advanced surface — distinct from their simple-mode siblings.
sql_create_table:
what: "Create a table using SQL syntax (advanced mode)."
example: "create table Customers (id int primary key, name text, email text)"
concept: "Advanced mode speaks SQL: constraints go inline (`primary key`, `not null`, `unique`, `check`). This is the raw form of simple mode's `create table … with pk …`."
sql_alter_table:
what: "Change a table's structure with SQL `alter table` (advanced mode)."
example: "alter table Customers add column phone text"
concept: "`alter table` adds or drops columns, renames, and adds constraints — the SQL equivalent of simple mode's `add column` / `drop column` / `change column`."
sql_create_index:
what: "Create an index with SQL (advanced mode)."
example: "create index ix_email on Customers (email)"
concept: "Add `unique` to also forbid duplicate values. The simple-mode equivalent is `add index`."
sql_drop_index:
what: "Remove an index with SQL (advanced mode)."
example: "drop index ix_email"
concept: "Only the lookup shortcut goes; the data is untouched. Add `if exists` to ignore a missing index."
sql_drop_table:
what: "Remove a table with SQL (advanced mode)."
example: "drop table Customers"
concept: "Add `if exists` to avoid an error when the table might not be there. Relationships pointing at it may block the drop."
sql_insert:
what: "Insert rows with SQL (advanced mode)."
example: "insert into Customers (name, email) values ('Ann', 'ann@example.io')"
concept: "Naming the columns lets you supply them in any order and skip ones that have a default — the SQL form of simple mode's `insert`."
sql_update:
what: "Update rows with SQL (advanced mode)."
example: "update Customers set email = 'new@example.io' where id = 1"
concept: "`set` lists the new values; `where` picks which rows change. The SQL form of simple mode's `update`."
sql_delete:
what: "Delete rows with SQL (advanced mode)."
example: "delete from Orders where status = 'cancelled'"
concept: "`where` picks the rows to remove; foreign-key rules still apply. The SQL form of simple mode's `delete`."
select:
what: "Query rows with SQL `select` (advanced mode)."
example: "select name, email from Customers where id = 1"
concept: "`select` is read-only: choose columns (or `*`), filter with `where`, sort with `order by`, cap with `limit`. This is the heart of SQL — and the reason advanced mode exists."
with:
what: "Name a sub-query (a CTE) and read from it in a `select` (advanced mode)."
example: "with recent as (select * from Orders where id > 100) select * from recent"
concept: "A `with` clause (Common Table Expression) names a query so the main `select` can use it like a temporary table — handy for breaking a complex query into readable steps."
explain_sql:
what: "Show how the database will run a SQL query, without running it (advanced mode)."
example: "explain select * from Customers where email = 'a@example.io'"
concept: "Like simple mode's `explain`, but wraps a raw SQL statement. It reveals whether an index is used, and never executes."
err:
# Runtime error classes (Phase C batch 5), keyed by
# friendly::error_hint_class. `example` is a fix recipe rather than a
# runnable line; `concept` is the relational idea behind the rule.
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`."
parent_side:
what: "You're deleting or changing a row that other rows point at, which would orphan those children."
example: "Delete the child rows first, or set the relationship's `on delete` to `cascade` (remove them too) or `set null` (keep them, unlinked)."
concept: "A foreign key guarantees every child has a real parent, so the database won't remove a parent out from under its children unless the relationship says what should happen to them."
unique:
what: "A value you're inserting — or updating to — already exists in a column that must be unique."
example: "Pick a different value, or update the existing row instead of inserting a new one."
concept: "A unique constraint (and every primary key) forbids duplicates, so each value identifies at most one row."
not_null:
what: "You left a column empty that is required to have a value."
example: "Supply a value for the column, or give it a default so new rows fill it automatically."
concept: "A `not null` constraint means every row must have a value there — it's how you mark a fact as mandatory."
check:
what: "A value broke a `check` rule defined on the column."
example: "Use a value the rule allows — for example a positive number, or one of the permitted options."
concept: "A `check` constraint is a condition every row must satisfy, so the database enforces business rules like \"price ≥ 0\" for you."
type_mismatch:
what: "A value doesn't fit the column's type — for instance text where a number is expected."
example: "Give a value of the right type: a number for `int`/`real`, a quoted string for `text`, true/false for `bool`."
concept: "Every column has a type, and the database rejects values that don't fit, so a column's data stays consistent and comparable."
not_found:
what: "You named a table or column that doesn't exist."
example: "Check the spelling, or run `show tables` (or `show table <name>`) to see what's there."
concept: "A command can only refer to tables and columns that already exist — create them first if you need them."
already_exists:
what: "You tried to create a table, column, relationship, or index whose name is already taken."
example: "Pick a different name, or drop the existing one first if you meant to replace it."
concept: "Names must be unique within their kind so a command is never ambiguous about what it refers to."
generic:
what: "The database refused the command for the reason shown above."
example: "Read that message for the specifics, adjust the command, and try again."
invalid_value:
what: "A value or option in the command wasn't valid for where it was used."
example: "Check the value against the column's type and the command's accepted options."
# 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 <table> ▮` 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 <T>` 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 <T>` 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 <name> …` 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 <name>` 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 <name>(<type>)` 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 <name>` prefix on UNIQUE has nowhere to go.
alter_named_unique: "a UNIQUE constraint cannot be named — use `alter table <T> add unique (<col>, …)` without `constraint <name>`."
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 <Name> with pk [<col>(<type>)[, ...]]"
create_m2n: "create m:n relationship from <Table1> to <Table2> [as <Name>]"
# 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] <Name> (<col> <type> [constraints], ...)"
sql_drop_table: "drop table [if exists] <Name>"
sql_create_index: "create [unique] index [if not exists] [<Name>] on <Table> (<col>[, ...])"
sql_drop_index: "drop index [if exists] <Name>"
sql_alter_table: |-
alter table <Table> add column <Name> <Type> [not null] [unique] [default <expr>] [check (<expr>)]
alter table <Table> drop column <Name>
alter table <Table> rename column <Old> to <New>
alter table <Table> rename to <NewName>
alter table <Table> alter column <Name> type <Type>
alter table <Table> add [constraint <Name>] check (<expr>) | unique (<col>, ...) | foreign key (<col>) references <Parent>[(<col>)]
alter table <Table> drop constraint <Name>
drop_table: "drop table <Name>"
drop_column: "drop column [from] [table] <Table>: <Name>"
drop_relationship: |-
drop relationship <Name>
drop relationship from <Parent>.<col> to <Child>.<col>
drop_index: |-
drop index <Name>
drop index on <Table> (<col>[, ...])
drop_constraint: "drop constraint (not null | unique | default | check) from <Table>.<col>"
add_column: "add column [to] [table] <Table>: <Name> (<Type>)"
add_relationship: |-
add 1:n relationship [as <Name>]
from <Parent>.<col> to <Child>.<col>
[on delete <action>] [on update <action>]
[--create-fk]
add_index: "add index [as <Name>] on <Table> (<col>[, ...])"
add_constraint: |-
add constraint not null to <Table>.<col>
add constraint unique to <Table>.<col>
add constraint default <value> to <Table>.<col>
add constraint check (<expr>) to <Table>.<col>
rename_column: "rename column [in] [table] <Table>: <Old> to <New>"
change_column: |-
change column [in] [table] <Table>: <Name> (<Type>)
[--force-conversion | --dont-convert]
seed: "seed <Table> [count] [set <col> = ... | in (...) | as <gen> | between x and y] | seed <Table>.<col>"
show_data: "show data <Table>"
show_table: "show table <Table>"
show_tables: "show tables"
show_relationships: "show relationships"
show_indexes: "show indexes"
show_relationship: "show relationship <name>"
show_index: "show index <name>"
insert: "insert into <Table> [(<col>[, ...])] [values] (<value>[, ...])"
update: "update <Table> set <col>=<value>[, ...] (where <col>=<value> | --all-rows)"
delete: "delete from <Table> (where <col>=<value> | --all-rows)"
explain: |-
explain show data <Table> [where <expr>] [limit <n>]
explain update <Table> set <col>=<value>[, ...] (where <expr> | --all-rows)
explain delete from <Table> (where <expr> | --all-rows)
explain <SQL select | with | insert | update | delete> (advanced mode)
replay: "replay <path> | replay '<path with spaces>'"
# SQL `SELECT` (advanced mode; ADR-0030 / ADR-0031).
select: "select (* | <expr>[ as <alias>][, ...]) from <Table> [where <expr>] [order by <expr>[ asc|desc][, ...]] [limit <n>]"
# 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] <Name> [(<col>[, ...])] as (<query>)[, ...] 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 [<command>]"
hint: "hint"
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"
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}.<column>` to reference one of its columns"
table_used_as_column: "`{name}` is a table — write `{name}.<column>` 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 [<path>]"
import_usage: "usage: import <zip-path> [as <target>]"
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
# `"<verb> <subject>" failed:` prefix was dropped — the echo line
# carries the ✗ marker and names the command.
failed: "{rendered}"
# Echo line `running: <input>` shown above each command's
# response while it executes; ADR-0040 resolves it to
# `<input> ✓` / `<input> ✗` on completion (the marker replaces
# the old `[ok] <verb> <subject>` 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 <zip> as <target>` 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"
hint: "hint"
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] <verb> <subject>` 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 <path>` 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"