From b34069f57c952e35ce97fee7d8efdbd381be2dc2 Mon Sep 17 00:00:00 2001 From: DanS Date: Mon, 13 Jul 2026 11:38:25 -0500 Subject: [PATCH] fix(console): don't silently drop a filled param after an empty optional MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The builder's "omit trailing optionals" loop stopped at the FIRST empty optional, which discarded any later field the user had filled — e.g. setban with bantime empty but absolute=true built `setban "ip" "add"`, dropping the typed `true`, and (worse) left Insert & run enabled so it ran a command that ignored the input. Include fields up to the last one that is filled or required; only genuinely trailing empty optionals are dropped. A gap (an empty field before a later filled one) keeps its placeholder and marks the command incomplete, so Insert & run disables and nothing typed is lost. Found by an adversarial pass over all 82 param templates. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/ui/windows/console_tab.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/ui/windows/console_tab.cpp b/src/ui/windows/console_tab.cpp index 27f34bd..4d67e8b 100644 --- a/src/ui/windows/console_tab.cpp +++ b/src/ui/windows/console_tab.cpp @@ -1635,13 +1635,20 @@ void ConsoleTab::renderCommandDetail(const ConsoleCommandEntry& cmd, const char* if (a == std::string::npos) return std::string(); return s.substr(a, s.find_last_not_of(" \t") - a + 1); }; + // Include fields up to the last one that is filled OR required; trailing empty optionals are + // dropped. An empty field that must still be included (a blank required field, or a gap before a + // later filled field — positional args can't skip a middle slot) keeps its placeholder and marks + // the command incomplete, so Insert & run stays disabled and no typed value is silently lost. + int lastNeeded = -1; + for (size_t k = 0; k < specs.size() && k < 6; k++) + if (!trimStr(cmd_param_bufs_[k]).empty() || !specs[k].optional) lastNeeded = (int)k; bool complete = specs.size() <= 6; std::string built = cmd.name; - for (size_t k = 0, stop = 0; k < specs.size() && k < 6 && !stop; k++) { + for (int k = 0; k <= lastNeeded; k++) { std::string val = trimStr(cmd_param_bufs_[k]); if (val.empty()) { - if (specs[k].optional) stop = 1; // omit this + trailing optionals - else { built += " " + specs[k].raw; complete = false; } // required placeholder + built += " " + specs[k].raw; + complete = false; } else { if (specs[k].type == "string" && val.front() != '"' && val.front() != '\'' && val.front() != '[' && val.front() != '{')