Help: consume CommandNode.help_id — REGISTRY-driven in-app help

Every CommandNode declared a help_id that nothing read; the in-app
`help` body was a single hand-kept catalog block that drifted from
the command set (handoff-12 §2.1).

note_help now iterates the command REGISTRY and translates each
CommandNode's help_id (`help.<id>`), framed by help.intro /
help.dsl_section / help.types_reference. A newly-registered command
appears in `help` automatically — no edit to note_help or a hand-kept
list. Added 20 per-command help entries plus the 3 framing entries;
removed help.in_app_body.

Per-command entries use block scalars: a libyml 0.0.5 scanner bug
panics on long internal space runs in double-quoted scalars, and the
entries are space-aligned.
This commit is contained in:
claude@clouddev1
2026-05-15 22:45:18 +00:00
parent f46606b12e
commit 03dd9003df
4 changed files with 139 additions and 56 deletions
+38 -14
View File
@@ -1595,22 +1595,46 @@ impl App {
} }
} }
/// Note a flat list of currently-supported app-level /// Note the list of currently-supported commands to the
/// commands to the output panel. /// output panel.
/// ///
/// This is the simple Iteration-4 stand-in for a richer /// Assembled from the command REGISTRY (ADR-0024 §help_id):
/// help system (H3 in the requirements doc); it gives the /// the framing (`help.intro`, `help.dsl_section`,
/// user a quick "what can I type?" reference that's /// `help.types_reference`) comes from the catalog, and each
/// always accurate against the build they're running. As /// command's body is the catalog entry named by its
/// new commands land, append them here. /// `help_id`. A newly-registered command appears here
/// automatically — no edit to this function or a hand-kept
/// list. Each catalog line becomes its own `OutputLine` so
/// the scroll-position math (one logical line = one display
/// row) stays accurate per the renderer's invariant.
fn note_help(&mut self) { fn note_help(&mut self) {
// Body lives in the i18n catalog (`help.in_app_body`). use crate::dsl::grammar::REGISTRY;
// Each YAML line becomes its own `OutputLine` so the
// scroll-position math (one logical line = one display let mut lines: Vec<String> = Vec::new();
// row) stays accurate per the renderer's invariant. lines.push(crate::t!("help.intro"));
let body = crate::t!("help.in_app_body"); // REGISTRY is ordered app-commands first; emit the
for line in body.lines() { // "DSL data commands" sub-header at the first command
self.note_system(line.to_string()); // whose help_id leaves the `app.` namespace.
let mut dsl_header_done = false;
for command in REGISTRY {
let Some(help_id) = command.help_id else {
continue;
};
if !dsl_header_done && !help_id.starts_with("app.") {
lines.push(crate::t!("help.dsl_section"));
dsl_header_done = true;
}
let key = format!("help.{help_id}");
let body = crate::friendly::translate(&key, &[]);
lines.extend(body.lines().map(str::to_string));
}
lines.extend(
crate::t!("help.types_reference")
.lines()
.map(str::to_string),
);
for line in lines {
self.note_system(line);
} }
} }
+5 -1
View File
@@ -351,7 +351,11 @@ pub struct CommandNode {
/// as a per-node validator (Phase A: none — every app /// as a per-node validator (Phase A: none — every app
/// command's ast_builder is infallible). /// command's ast_builder is infallible).
pub ast_builder: fn(&MatchedPath) -> Result<Command, ValidationError>, pub ast_builder: fn(&MatchedPath) -> Result<Command, ValidationError>,
#[allow(dead_code)] /// Catalog key (`help.<id>`) for this command's in-app
/// `help` entry. Consumed by `App::note_help`, which
/// iterates the REGISTRY and translates each `help_id` —
/// so a newly-registered command appears in `help`
/// automatically (ADR-0024 §help_id).
pub help_id: Option<&'static str>, pub help_id: Option<&'static str>,
/// Catalog keys under `parse.usage.*` to render in the /// Catalog keys under `parse.usage.*` to render in the
/// "usage:" block when a parse error fires for this command /// "usage:" block when a parse error fires for this command
+25 -1
View File
@@ -120,7 +120,31 @@ pub const KEYS_AND_PLACEHOLDERS: &[(&str, &[&str])] = &[
), ),
// ---- Help text ---- // ---- Help text ----
("help.cli_banner", &[]), ("help.cli_banner", &[]),
("help.in_app_body", &[]), // In-app `help` — framing + per-command entries keyed by
// each CommandNode's `help_id` (ADR-0024 §help_id).
("help.intro", &[]),
("help.dsl_section", &[]),
("help.types_reference", &[]),
("help.app.quit", &[]),
("help.app.help", &[]),
("help.app.rebuild", &[]),
("help.app.save", &[]),
("help.app.new", &[]),
("help.app.load", &[]),
("help.app.export", &[]),
("help.app.import", &[]),
("help.app.mode", &[]),
("help.app.messages", &[]),
("help.ddl.create", &[]),
("help.ddl.drop", &[]),
("help.ddl.add", &[]),
("help.ddl.rename", &[]),
("help.ddl.change", &[]),
("help.data.show", &[]),
("help.data.insert", &[]),
("help.data.update", &[]),
("help.data.delete", &[]),
("help.data.replay", &[]),
// ---- Hint panel ambient typing assistance (ADR-0022 §6) ---- // ---- Hint panel ambient typing assistance (ADR-0022 §6) ----
("hint.ambient_complete", &[]), ("hint.ambient_complete", &[]),
( (
+71 -40
View File
@@ -208,46 +208,77 @@ help:
import <zip> [as <t>] Unpack <zip> into a new project and import <zip> [as <t>] Unpack <zip> into a new project and
switch to it. <t> overrides the target switch to it. <t> overrides the target
name (else taken from the zip). name (else taken from the zip).
# In-app `help` command output. Same shape as # In-app `help` command output (ADR-0024 §help_id). The
# `cli_banner` — multi-line block, consumers iterate # renderer iterates the command REGISTRY and translates each
# lines and emit each as its own output row so scroll # CommandNode's `help_id` — so a newly-registered command
# math stays accurate. # appears in `help` automatically, with no edit here. `intro`
in_app_body: | # and `dsl_section` frame the two command groups; each
Supported commands: # `app.*` / `ddl.*` / `data.*` entry is keyed by a command's
quit — exit # `help_id`; `types_reference` closes the block. All entries
help — this list # are multi-line-capable — the renderer emits one output row
mode simple|advanced — switch input mode # per line so scroll math stays accurate.
messages — show current verbosity intro: "Supported commands:"
messages short|verbose— switch error wording (verbose is the default) dsl_section: "DSL data commands (in simple mode):"
rebuild — rebuild .db from project.yaml + data/ (with confirmation) # Per-command help, keyed by `CommandNode.help_id`. Block
save — save current temp project under a name # scalars (`|-`) so the column alignment survives — the
save as — copy current project to a new name/path # double-quoted form trips a libyml scanner bug on long
new — close current, start a fresh temp project # internal space runs. The renderer emits one output row per
load — open the project picker # line, so multi-form commands list each form on its own line.
export [<path>] — write a zip of project.yaml + data/ (excludes .db, history.log) app:
import <zip> [as <t>] — unpack a zip and switch to the new project quit: |-
DSL data commands (in simple mode): quit — exit the app
create table <T> with pk [<col>:<type>...] help: |-
drop table <T> help — show this command list
add column [to] [table] <T>: <col> (<type>) rebuild: |-
(for serial/shortid on a non-empty table: existing rows auto-filled) rebuild — rebuild the project database from project.yaml + data/ (with confirmation)
drop column [from] [table] <T>: <col> save: |-
rename column [in] [table] <T>: <old> to <new> save — save the current temp project under a name
change column [in] [table] <T>: <col> (<newtype>) save as — copy the current project to a new name/path
[--force-conversion | --dont-convert] new: |-
(to serial/shortid: null cells auto-filled with generated values) new — close the current project, start a fresh temp project
add 1:n relationship [as <name>] from <P>.<col> to <C>.<col> load: |-
[on delete <action>] [on update <action>] [--create-fk] load — open the project picker
drop relationship <name> export: |-
insert into <T> [(cols)] [values] (vals) export [<path>] — write a zip of project.yaml + data/ (excludes the database file and history.log)
update <T> set <c>=<v>... where <c>=<v> | --all-rows import: |-
delete from <T> where <c>=<v> | --all-rows import <zip> [as <target>] — unpack a zip into a new project and switch to it
show table <T> mode: |-
show data <T> mode simple|advanced — switch input mode
replay <path> — run each non-blank, non-`#`-comment line messages: |-
of <path> as a command. Stops at the first messages [short|verbose] — show or switch error-message verbosity (verbose is the default)
error (no rollback). Relative paths resolve ddl:
under the current project's directory. create: |-
create table <T> with pk [<col>:<type>, ...] — create a table
drop: |-
drop table <T> — remove a table
drop column [from] [table] <T>: <col> — remove a column
drop relationship <name> — remove a relationship
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
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
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.
# Type reference, appended after the command list.
types_reference: |
Types: text, int, real, decimal, bool, date, datetime, blob, serial, shortid Types: text, int, real, decimal, bool, date, datetime, blob, serial, shortid
Auto-generated types (serial, shortid): Auto-generated types (serial, shortid):
serial — integer that auto-fills with the next sequence value serial — integer that auto-fills with the next sequence value