fix(web): a partial save no longer switches off checkboxes it never showed

An HTML checkbox posts nothing when unchecked, so the save route walked the
schema and forced every boolean missing from the form to False. That is right
for the rendered form and wrong for every other caller: a script, the MQTT
bridge or a curl against the documented endpoint never rendered a checkbox, and
reading its silence as "all off" turns a one-field save into a mass disable.

Found on hardware. Posting four customization.* keys to a live device switched
off nfl.enabled, ncaa_fb.enabled and every display-mode toggle in one request.

The form now reports the top-level sections it drew (__rendered_section), and
inside those an absent checkbox still means unchecked -- including a section
whose only fields are checkboxes that are all off, which no heuristic could
recover. A post with no marker only touches objects it actually posted a field
from. Meta fields are dropped before form keys are treated as config paths,
because unknown keys are otherwise written straight into config.json.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Chuck
2026-09-13 10:39:51 -04:00
co-authored by Claude Opus 5
parent f9e9eeba00
commit 1df51b172a
4 changed files with 335 additions and 9 deletions
@@ -1191,6 +1191,14 @@
{% endif %}
{% endif %}
{% endfor %}
{# Tell the save path which sections this form actually
drew. An unchecked checkbox posts nothing, so without
this the server cannot tell "the user cleared it"
from "the caller never had that field" -- and a
partial post would read as every box being off. #}
{% for key in tiers.basic + tiers.advanced %}
<input type="hidden" name="__rendered_section" value="{{ key }}">
{% endfor %}
{% for key in tiers.basic %}
{% set prop = schema.properties[key] %}
{% set value = config[key] if key in config else none %}