Skip to content

fix(ui): stop SwitchWithFormSupport from double-submitting under one name (PP-msjp) - #2125

Open
timothyfroehlich wants to merge 2 commits into
mainfrom
claude/vibrant-faraday-2fs2gm
Open

timothyfroehlich wants to merge 2 commits into
mainfrom
claude/vibrant-faraday-2fs2gm

Conversation

@timothyfroehlich

@timothyfroehlich timothyfroehlich commented Sep 15, 2026

Copy link
Copy Markdown
Owner

What

SwitchWithFormSupport passed name to both the Radix Root and its own custom hidden <input>. Inside a form, Radix's Root submits its own bubble input (value "on"), so an enabled, checked switch submitted two FormData entries under one name — formData.getAll(name) returned ["on", "on"].

It was latent because the only consumer (updateNotificationPreferencesAction) reads formData.get(name) — the first value. The trap: switching that read to .getAll(), or any ordering change that let the hidden input's explicit "off" be shadowed, would have been surprised. (Filed as PP-msjp from a PR #2059 review; deliberately not fixed there to keep that change scoped.)

Fix (bead option a)

Make the Radix Root nameless so the custom hidden input is the sole FormData carrier. name is destructured out of props, so it never reaches the Root. The hidden input already emits the explicit "off" that Radix's bubble cannot, and already renders nothing while disabled (PP-bhd7.7), so every submission case is preserved and now yields exactly one entry:

state getAll(name) before after
enabled + checked ["on", "on"] ["on"]
enabled + unchecked ["off"] ["off"]
disabled [] []
no name [] []

No consumer changes: only notification-preferences-form.tsx passes a name, and it reads .get(). The other four importers drive the switch purely via checked/onCheckedChange with no name, so they never touched FormData.

Tests

Extended the existing switch.test.tsx regression suite with a getAll()-based assertion for the checked case — the prior tests all used .get(), so none could catch the duplicate. Verified non-vacuous: the new assertion reads ["on", "on"] against the pre-fix code. Also added a caution comment on the base (unexported) Switch, which has the same double-submit shape if ever given a name in a form.

Scope

Two files. Follow-up PP-r5xz filed for an adjacent, pre-existing latent observation (the hidden input derives from React state, so it ignores a native <form> reset) — out of scope for this P3.

Review

Reviewed adversarially by subagents across correctness, bead-fidelity, and maintainability lenses; their findings (a vacuous unchecked test, a comment-hardening suggestion, and a "renders" → "submits" precision nit) are folded in.

Opened by the unattended nightly bead session, so it carries ownerless — nobody is driving its CI or review threads until an attended session adopts it.

🤖 Generated with Claude Code

https://claude.ai/code/session_01SqqP7PcYtf1yq1Upa3k82m


Generated by Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Fixed form submissions for named switches to prevent duplicate values.
    • Named switches now submit exactly one value: "on" when enabled or "off" when disabled.

…name (PP-msjp)

A named SwitchWithFormSupport passed `name` to both the Radix Root and its
own custom hidden input, so an enabled, checked switch submitted two FormData
entries under one name: Radix's form bubble ("on") and the hidden input
("on"). formData.getAll(name) therefore returned ["on", "on"]. It was latent
because the only consumer (updateNotificationPreferencesAction) reads
.get() (the first value), but a switch to .getAll() — or any ordering change
that let the hidden input's explicit "off" be shadowed — would have been
surprised.

Fix (bead option a): make the Radix Root nameless so the custom hidden input
is the sole FormData carrier. It already emits the explicit "off" that Radix's
bubble cannot, and it already renders nothing while disabled, so every
submission case is preserved and now yields exactly one entry.

Extend the existing switch regression tests with getAll()-based assertions
(the prior tests all used .get(), so none caught the duplicate).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqqP7PcYtf1yq1Upa3k82m
… renders (PP-msjp)

Review nit: the Radix bubble input renders whenever the switch is inside a
form regardless of `name`; `name` controls whether it is *submitted*. Say
"submits" so a future debugger isn't surprised to find the nameless bubble
still in the DOM. Comment-only.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqqP7PcYtf1yq1Upa3k82m
@vercel

vercel Bot commented Sep 15, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
pin-point Ready Ready Preview Sep 15, 2026 7:48am UTC

Request Review

@timothyfroehlich timothyfroehlich added the ownerless Opened by an unattended agent (routine, Dependabot, Renovate); needs pickup label Sep 15, 2026 — with Claude
@coderabbitai

coderabbitai Bot commented Sep 15, 2026

Copy link
Copy Markdown

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Advanced

Run ID: c506a11f-9721-483d-8a98-e846a5147e38

📥 Commits

Reviewing files that changed from the base of the PR and between ebb642f and 71354cf.

📒 Files selected for processing (2)
  • src/components/ui/switch.tsx
  • src/test/unit/components/ui/switch.test.tsx

Included review availability: Your plan provides up to 10 included reviews per hour; 9 remain after this review.


📝 Walkthrough

Walkthrough

The form-supported Switch no longer passes name to the Radix Root. The hidden input remains the sole form submission source. Documentation and a regression test verify one "on" value for a checked named Switch.

Changes

Switch form submission

Layer / File(s) Summary
Prevent duplicate Switch submissions
src/components/ui/switch.tsx, src/test/unit/components/ui/switch.test.tsx
The documentation distinguishes the base Switch from SwitchWithFormSupport. The Radix Root no longer receives name. The regression test verifies that a checked named Switch produces exactly one "on" entry in FormData.

Priority: ⬇️ Low

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

Change: Bug fix · Severity of issue fixed: Low

Merge Risk: ⚪ Minimal · up to 71354

The Switch submits one correct value while preserving unchecked and disabled behavior, with no remaining merge-blocking risk.

🚥 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 and concisely describes the main change: preventing duplicate form submission by SwitchWithFormSupport. It includes the relevant component and issue identifier.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 2…
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 claude/vibrant-faraday-2fs2gm

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ownerless Opened by an unattended agent (routine, Dependabot, Renovate); needs pickup

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants