Fix: array-table boolean columns always saved as unchecked (countdown 'No Active' bug)#381
Conversation
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
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthroughHidden 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 ChangesBoolean checkbox sentinel sync
Estimated code review effort: 2 (Simple) | ~10 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
Up to standards ✅🟢 Issues
|
| Metric | Results |
|---|---|
| Complexity | 0 |
| Duplication | 0 |
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.
|
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 |
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-tablewidget'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 samenameas the visible checkbox. The hidden was hardcoded tovalue="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 actualenabledstate; 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-countdownenabledand the custom-feeds widget's per-feedenabled) — sets the hidden's initial value from the actual data, and syncs it viaonchangeon 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 achangelistener.Scope check
Audited every other checkbox/hidden-input pattern in
plugin_config.htmland confirmed they're unrelated or already safe:checkbox-groupwidget (array-bracket names, its own explicit sync already wired)hx-post, not part of the main save form)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 inplugin_config.htmlwas, which is fixed above.Test plan
python3 -c "from jinja2 import Environment, FileSystemLoader; ..."— full template still parsesenabled: Trueandenabled: False— hidden and checkbox now agree in both cases (previously hidden was always"false")getValue()read path is untouched)🤖 Generated with Claude Code
https://claude.ai/code/session_01KEZK1P1Q1fu5pcuVrkrCFZ
Summary by CodeRabbit
true/falsevalues from being submitted.