Skip to content

Fix: array-table boolean columns always saved as unchecked (countdown 'No Active' bug)#381

Merged
ChuckBuilds merged 1 commit into
mainfrom
fix/array-table-boolean-checkbox-save
Jul 6, 2026
Merged

Fix: array-table boolean columns always saved as unchecked (countdown 'No Active' bug)#381
ChuckBuilds merged 1 commit into
mainfrom
fix/array-table-boolean-checkbox-save

Conversation

@ChuckBuilds

@ChuckBuilds ChuckBuilds commented Jul 5, 2026

Copy link
Copy Markdown
Owner

Summary

Reported bug: the countdown plugin always shows "No Active" even with a countdown configured and enabled — because every save silently unchecked it ("everytime I save it, it unchecks my timer").

Root cause: the array-table widget's boolean column renders a hidden sentinel input (needed so unchecked checkboxes still submit "false", since browsers omit unchecked checkboxes from form submission entirely) sharing the same name as the visible checkbox. The hidden was hardcoded to value="false" with no sync to the checkbox's actual state. Whichever of the two same-named inputs the save request's form-collection logic happens to prefer, the hidden's stale "false" could silently override an actually-checked box on every save — independent of what the user set.

Confirmed via an isolated Jinja render of the exact snippet: before the fix, the hidden was always value="false" regardless of the countdown's actual enabled state; after the fix, it correctly matches ("true" when checked, "false" when not).

Changes

  • 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) — 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.

Scope check

Audited every other checkbox/hidden-input pattern in plugin_config.html and confirmed they're unrelated or already safe:

  • Default single-checkbox renderer (no hidden pair — relies on standard omission-means-false)
  • checkbox-group widget (array-bracket names, its own explicit sync already wired)
  • The array-table advanced-props modal editor (single hidden per name, explicitly written on Save button click)
  • The top-level plugin enable/disable toggle (separate immediate hx-post, not part of the main save form)
  • 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 by this bug either — only its server-rendered counterpart in plugin_config.html was, which is fixed above.

Test plan

  • python3 -c "from jinja2 import Environment, FileSystemLoader; ..." — full template still parses
  • Isolated Jinja render of the exact fixed snippet with enabled: True and enabled: False — hidden and checkbox now agree in both cases (previously hidden was always "false")
  • Manual review of the JS diff (no JS runtime available in this environment to execute it directly, but the change is a small, mechanical mirror of the same fix applied to the Jinja template, plus the already-correct getValue() read path is untouched)

🤖 Generated with Claude Code

https://claude.ai/code/session_01KEZK1P1Q1fu5pcuVrkrCFZ

Summary by CodeRabbit

  • Bug Fixes
    • Fixed checkbox-based boolean fields in table-style forms so saved values now match the current on-screen state.
    • Improved handling of hidden companion inputs to prevent outdated true/false values from being submitted.
    • Applied the fix across both generic table widgets and plugin configuration tables.

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.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KEZK1P1Q1fu5pcuVrkrCFZ
@coderabbitai

coderabbitai Bot commented Jul 5, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 449ec7ec-cd00-4fb4-997b-574af7d6765e

📥 Commits

Reviewing files that changed from the base of the PR and between c6ce332 and 37e15c2.

📒 Files selected for processing (2)
  • web_interface/static/v3/js/widgets/array-table.js
  • web_interface/templates/v3/partials/plugin_config.html

📝 Walkthrough

Walkthrough

Hidden sentinel inputs paired with boolean checkboxes in array-table.js and plugin_config.html are updated to initialize their value from the actual current state instead of a hard-coded false, and change/onchange handlers now sync the hidden input's value whenever the checkbox is toggled.

Changes

Boolean checkbox sentinel sync

Layer / File(s) Summary
Array-table widget sentinel handling
web_interface/static/v3/js/widgets/array-table.js
Hidden sentinel input for boolean columns is initialized from colValue and updated via a change listener when the checkbox is toggled.
Plugin config template sentinel handling
web_interface/templates/v3/partials/plugin_config.html
The custom-feeds "enabled" hidden input and the generic array-table boolean sentinel are initialized from actual item/cell values and kept in sync via onchange handlers.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

  • ChuckBuilds/LEDMatrix#216: Both PRs modify boolean checkbox handling in plugin_config.html to correctly persist unchecked/false values.
  • ChuckBuilds/LEDMatrix#356: Both PRs modify the array-table hidden sentinel/checkbox sync logic in array-table.js and plugin_config.html.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the bug fix for array-table boolean columns being saved as unchecked, including the related countdown issue.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/array-table-boolean-checkbox-save

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codacy-production

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 0 complexity · 0 duplication

Metric Results
Complexity 0
Duplication 0

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

@ChuckBuilds

Copy link
Copy Markdown
Owner Author

Follow-up verification: traced the exact server-side mechanism that made this bug happen, for anyone reviewing later.

`plugin_config.html`'s save form has no `hx-ext="json-enc"` (unlike several other forms in this app), so it's a plain form submission — the backend genuinely reads `request.form` as a Werkzeug MultiDict. `save_plugin_config()` (api_v3.py:4342) does `form_data = request.form.to_dict()`, which uses Werkzeug's default `flat=True` — first value wins for duplicate keys. Since the hidden sentinel is always first in DOM order (and thus first in the submitted body), its value was always what `to_dict()` picked up, regardless of the checkbox's actual state — exactly matching the reported symptom.

Also checked this doesn't overlap with #216 (merged Jan 2026, added `_set_missing_booleans_to_false`) — that function only acts when a boolean's key is entirely absent from the form. Because the hidden sentinel guarantees the key is always technically present, #216's fix never triggers for this case; it solves a different, non-overlapping scenario (checkboxes with no hidden fallback at all). No redundancy, both fixes are needed for their respective mechanisms.

No other occurrences of this pattern remain anywhere in the codebase (re-swept type="hidden".*value="false" across templates and JS widgets after the fix).

@ChuckBuilds ChuckBuilds merged commit deaa3d7 into main Jul 6, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant