fix: array-table boolean columns always saved as unchecked (#381)

Reported bug: countdown plugin always shows "No Active" even with a
countdown configured and enabled — because every save silently unchecked
it. Root cause: the boolean column's hidden-sentinel input (needed so
unchecked checkboxes still submit "false", since browsers omit unchecked
checkboxes entirely) was hardcoded to value="false" and shared the same
`name` as the checkbox, with no sync between them. Whichever of the two
same-named inputs the save request's form-collection happens to prefer,
the hidden's stale "false" could silently override an actually-checked
box on every save, independent of what the user set.

Fixed in both places this pattern is rendered:
- plugin_config.html: the initial server-rendered row for every array-table
  boolean column (affects countdown's per-countdown "enabled" and the
  custom-feeds widget's per-feed "enabled") — now sets the hidden's initial
  value from the actual data, and syncs it via onchange on every toggle.
- array-table.js: the client-side row renderer used when adding/rebuilding
  rows in the browser — same fix, keeping both inputs in sync at render
  time and via a change listener.

Verified other checkbox/hidden-input patterns in plugin_config.html and
confirmed they're unrelated/already-safe: the default single-checkbox
renderer (no hidden pair), checkbox-group (array-bracket names with its
own explicit sync), the array-table advanced-props modal editor (single
hidden per name, explicitly written on Save), the top-level plugin
enable/disable toggle (separate immediate hx-post), and the no-schema
fallback checkbox. custom-feeds.js's own client-side row renderer never
creates a hidden sentinel at all, so it isn't affected either.

Verified the fix's rendered output directly with an isolated Jinja render
of the exact snippet: hidden and checkbox now agree ("true"/checked or
"false"/unchecked) for both states, where before the hidden was always
"false" regardless of the actual value.


Claude-Session: https://claude.ai/code/session_01KEZK1P1Q1fu5pcuVrkrCFZ

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Chuck
2026-07-06 08:05:06 -04:00
committed by GitHub
co-authored by Claude Sonnet 5
parent cbb8ec41e8
commit deaa3d7a98
2 changed files with 22 additions and 6 deletions
@@ -450,15 +450,16 @@
</div>
</td>
<td class="px-4 py-3 whitespace-nowrap text-center">
<input type="hidden" name="{{ full_key }}.{{ item_index }}.enabled" value="false">
<input type="checkbox"
<input type="hidden" name="{{ full_key }}.{{ item_index }}.enabled" value="{{ 'true' if item.get('enabled', true) else 'false' }}">
<input type="checkbox"
name="{{ full_key }}.{{ item_index }}.enabled"
{% if item.get('enabled', true) %}checked{% endif %}
value="true"
onchange="this.previousElementSibling.value = this.checked ? 'true' : 'false'"
class="h-4 w-4 text-blue-600">
</td>
<td class="px-4 py-3 whitespace-nowrap text-center">
<button type="button"
<button type="button"
onclick="removeCustomFeedRow(this)"
class="text-red-600 hover:text-red-800 px-2 py-1">
<i class="fas fa-trash"></i>
@@ -549,11 +550,18 @@
{% else %}{% set td_min_w = '110px' %}{% endif %}
<td class="px-3 py-3 whitespace-nowrap" style="min-width:{{ td_min_w }};vertical-align:middle">
{% if col_type == 'boolean' %}
<input type="hidden" name="{{ full_key }}.{{ item_index }}.{{ col_name }}" value="false">
{# Hidden sentinel ensures unchecked boxes still submit "false" (browsers
omit unchecked checkboxes entirely). It shares the checkbox's `name`,
so it must always mirror the checkbox's actual state — both here at
render time and on every toggle — or whichever of the two same-named
inputs the save request happens to prefer can silently revert this
field to false regardless of what's checked. #}
<input type="hidden" name="{{ full_key }}.{{ item_index }}.{{ col_name }}" value="{{ 'true' if col_value else 'false' }}">
<input type="checkbox"
name="{{ full_key }}.{{ item_index }}.{{ col_name }}"
{% if col_value %}checked{% endif %}
value="true"
onchange="this.previousElementSibling.value = this.checked ? 'true' : 'false'"
class="h-4 w-4 text-blue-600">
{% elif col_type == 'integer' or col_type == 'number' %}
<input type="number"