diff --git a/.claude/launch.json b/.claude/launch.json new file mode 100644 index 0000000..1c1194c --- /dev/null +++ b/.claude/launch.json @@ -0,0 +1,18 @@ +{ + "version": "0.0.1", + "configurations": [ + { + "name": "web-viewer", + "runtimeExecutable": ".venv/bin/python", + "runtimeArgs": [ + "-m", + "modules.web_viewer.app", + "--config", + "/private/tmp/claude-501/-Users-adam-meshcore-bot/31648a90-50b9-4cae-b1f6-0e7815c5a855/scratchpad/viewer/config.ini", + "--port", + "8757" + ], + "port": 8757 + } + ] +} diff --git a/meshcore_bot.py b/meshcore_bot.py index 469721d..b122f66 100644 --- a/meshcore_bot.py +++ b/meshcore_bot.py @@ -113,6 +113,31 @@ def main(): print(f"Info: {message}", file=sys.stderr) sys.exit(1 if has_error else 0) + # Always sanity-check the config on normal startup too — misspelled + # sections/keys otherwise fail silently and are the most common source of + # "why doesn't my setting work" confusion. Non-fatal: warn and continue. + try: + from modules.config_validation import ( + SEVERITY_ERROR as _SEV_ERR, + SEVERITY_WARNING as _SEV_WARN, + validate_config as _startup_validate, + ) + _issues = [ + (sev, msg) for sev, msg in _startup_validate(args.config) + if sev in (_SEV_ERR, _SEV_WARN) + ] + _MAX_SHOWN = 15 + for sev, msg in _issues[:_MAX_SHOWN]: + print(f"Config {sev}: {msg}", file=sys.stderr) + if len(_issues) > _MAX_SHOWN: + print( + f"Config: ... and {len(_issues) - _MAX_SHOWN} more issue(s); " + "run with --validate-config for the full report", + file=sys.stderr, + ) + except Exception as exc: # noqa: BLE001 - never block startup on the linter itself + print(f"Config validation skipped: {exc}", file=sys.stderr) + from modules.core import MeshCoreBot bot = MeshCoreBot(config_file=args.config) diff --git a/modules/ini_writer.py b/modules/ini_writer.py index a74a745..d9eb775 100644 --- a/modules/ini_writer.py +++ b/modules/ini_writer.py @@ -148,7 +148,12 @@ def update_ini_values( def _record_section_boundary() -> None: if current_norm is not None and current_norm in remaining: - section_body_end[current_norm] = len(out) + # Walk back over trailing blank lines so appended keys sit directly + # under the section's last entry instead of after the gap. + end = len(out) + while end > 0 and out[end - 1].strip() == "": + end -= 1 + section_body_end[current_norm] = end for raw in lines: section_match = _SECTION_RE.match(raw)