feat(console): lite backend command-reference modal

Give the lite console the parity analog of the full-node RPC command
reference: the same searchable two-pane modal (browse/search, detail
pane, examples, Insert / Insert & run, destructive confirm), driven by
the lite backend's own command set instead of daemon RPC.

- ConsoleCommandExecutor::commandReference() returns the category table
  (full node = consoleCommandCategories(); lite = new
  liteConsoleCommandCategories() -- 25 backend verbs in 5 categories).
  The shared renderCommandsPopup reads the table from the executor.
- The Commands button now shows for both variants (gated on
  commandReference()!=nullptr); title/tooltip/arg-quoting branch on
  hasRpcReference() (clarified to mean "speaks JSON-RPC / full node").
- Lite args are bare tokens (the backend takes one unsplit arg string
  and does not strip quotes), so the param-builder's JSON string
  auto-quoting is disabled for lite -- Insert & run emits runnable bare
  commands. send uses the JSON-array form (its positional form is
  unreachable via the single-arg transport); new uses zs/R; import
  takes just the key (the backend hardcodes birthday=0).

Adds 7 i18n keys across all 8 languages (additive). Full-node behavior
is unchanged. Adversarially reviewed (data vs the backend registry,
plumbing/regression, i18n) with all findings fixed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-19 12:27:51 -05:00
co-authored by Claude Opus 4.8
parent 567732206e
commit 5c570613c8
16 changed files with 248 additions and 23 deletions
+33 -12
View File
@@ -464,12 +464,18 @@ void ConsoleTab::render(ConsoleCommandExecutor& exec)
ImGui::EndChild();
}
void ConsoleTab::renderCommandsPopupModal()
void ConsoleTab::renderCommandsPopupModal(ConsoleCommandExecutor* exec)
{
if (!show_commands_popup_) {
return;
}
renderCommandsPopup();
// Need a backend that offers a reference table. If the console hasn't built its executor yet
// (popup can't have been opened normally) or the backend has none, just dismiss.
if (!exec || !exec->commandReference()) {
show_commands_popup_ = false;
return;
}
renderCommandsPopup(*exec);
}
void ConsoleTab::renderToolbar(ConsoleCommandExecutor& exec)
@@ -544,14 +550,16 @@ void ConsoleTab::renderToolbar(ConsoleCommandExecutor& exec)
ImGui::SameLine();
// Commands reference button (full-node RPC reference only)
if (exec.hasRpcReference()) {
// Commands reference button — shown whenever the backend offers a reference table (full-node
// JSON-RPC commands, or the lite backend's own verbs).
if (exec.commandReference()) {
if (TactileButton(TR("console_commands"), ImVec2(0, 0), schema::UI().resolveFont("button"))) {
command_search_[0] = '\0'; // fresh search each open (dismiss paths don't all reset it)
show_commands_popup_ = true;
}
if (ImGui::IsItemHovered()) {
material::Tooltip("%s", TR("console_show_rpc_ref"));
material::Tooltip("%s", exec.hasRpcReference() ? TR("console_show_rpc_ref")
: TR("console_show_backend_ref"));
}
ImGui::SameLine();
}
@@ -1561,6 +1569,11 @@ const char* consoleCategoryLabel(const char* name)
if (!std::strcmp(name, "Wallet")) return TR("console_cat_wallet");
if (!std::strcmp(name, "Raw Transactions")) return TR("console_cat_raw_transactions");
if (!std::strcmp(name, "Utility")) return TR("console_cat_utility");
// Lite backend reference categories.
if (!std::strcmp(name, "Sync")) return TR("console_cat_sync");
if (!std::strcmp(name, "Send")) return TR("console_cat_send");
if (!std::strcmp(name, "Keys & Security")) return TR("console_cat_keys");
if (!std::strcmp(name, "Advanced")) return TR("console_cat_advanced");
return name;
}
} // namespace
@@ -1580,7 +1593,7 @@ void ConsoleTab::insertCommandToInput(const ConsoleCommandEntry& cmd)
show_commands_popup_ = false;
}
void ConsoleTab::renderCommandDetail(const ConsoleCommandEntry& cmd, const char* catLabel)
void ConsoleTab::renderCommandDetail(const ConsoleCommandEntry& cmd, const char* catLabel, bool jsonArgs)
{
using namespace material;
float dp = Layout::dpiScale();
@@ -1631,8 +1644,11 @@ void ConsoleTab::renderCommandDetail(const ConsoleCommandEntry& cmd, const char*
ImGui::PopFont();
ImGui::SameLine(labelW);
ImGui::SetNextItemWidth(-1);
// Lite backend args are bare freeform tokens, so its param types (string/number) don't
// apply — show a neutral "value" hint there instead of a misleading "number".
std::string typeHint = jsonArgs ? s.type : std::string(TR("console_ref_value"));
std::string hint = s.optional
? (s.type + " \xC2\xB7 " + std::string(TR("console_ref_optional"))) : s.type;
? (typeHint + " \xC2\xB7 " + std::string(TR("console_ref_optional"))) : typeHint;
ImGui::InputTextWithHint("##pv", hint.c_str(), cmd_param_bufs_[k], sizeof(cmd_param_bufs_[k]));
ImGui::PopID();
}
@@ -1662,7 +1678,9 @@ void ConsoleTab::renderCommandDetail(const ConsoleCommandEntry& cmd, const char*
built += " " + specs[k].raw;
complete = false;
} else {
if (specs[k].type == "string" && val.front() != '"' && val.front() != '\'' &&
// JSON-RPC (full node) auto-quotes string args; the lite backend takes bare tokens, so
// leave the value exactly as typed there (quoting would break its address/key parsing).
if (jsonArgs && specs[k].type == "string" && val.front() != '"' && val.front() != '\'' &&
val.front() != '[' && val.front() != '{')
val = "\"" + val + "\"";
built += " " + val;
@@ -1735,13 +1753,16 @@ void ConsoleTab::renderCommandDetail(const ConsoleCommandEntry& cmd, const char*
}
}
void ConsoleTab::renderCommandsPopup()
void ConsoleTab::renderCommandsPopup(ConsoleCommandExecutor& exec)
{
using namespace material;
float dp = Layout::dpiScale();
// Full node speaks JSON-RPC (quote string args); the lite backend takes bare tokens.
const bool jsonArgs = exec.hasRpcReference();
material::OverlayDialogSpec ov;
ov.title = TR("console_rpc_reference");
ov.title = jsonArgs ? TR("console_rpc_reference") : TR("console_backend_reference");
ov.p_open = &show_commands_popup_;
ov.style = material::OverlayStyle::BlurFloat; // floating content on the blur, plain heading
ov.cardWidth = 960.0f; // wide enough for two panes
@@ -1770,7 +1791,7 @@ void ConsoleTab::renderCommandsPopup()
std::transform(q.begin(), q.end(), q.begin(), ::tolower);
const bool searching = !q.empty();
const auto& categories = consoleCommandCategories();
const auto& categories = *exec.commandReference(); // non-null: guarded by renderCommandsPopupModal
// Flat display order of (cat,idx): ranked when searching, category order when browsing. Drives
// keyboard nav + auto-selection; the browse view still renders grouped headers below.
@@ -1912,7 +1933,7 @@ void ConsoleTab::renderCommandsPopup()
if (cmd_sel_cat_ >= 0 && cmd_sel_cat_ < (int)categories.size() &&
cmd_sel_idx_ >= 0 && cmd_sel_idx_ < categories[cmd_sel_cat_].count) {
renderCommandDetail(categories[cmd_sel_cat_].commands[cmd_sel_idx_],
consoleCategoryLabel(categories[cmd_sel_cat_].name));
consoleCategoryLabel(categories[cmd_sel_cat_].name), jsonArgs);
} else {
ImVec2 av = ImGui::GetContentRegionAvail();
ImGui::SetCursorPosY(av.y * 0.4f);