Fix client router intercepting JS-handled forms (chat/comments navigate on send)#151
Merged
Conversation
…apture The client router's submit listener was on document in CAPTURE phase, but a component's @submit handler is bound per-element and runs at-target, AFTER document capture. So for a JS-handled form (the blog's live chat and comments call e.preventDefault() and send over WebSocket/fetch), the router's onSubmit fired first, its `if (e.defaultPrevented) return` guard saw false (the component had not run yet), and it intercepted the form: with no action it fell back to the current URL and navigated/swapped the page. Result: the message was sent AND the page swapped, scrolling to the top and wiping the WebSocket-driven chat state, so the just-sent message vanished. Register submit in the bubble phase (capture false) so the component's @submit runs first; its preventDefault then makes the guard skip the form, honoring the documented "forms that preventDefault in @submit are untouched" contract. Plain progressive-enhancement forms (no @submit) still get intercepted and SPA-submitted. Click stays capture. Mirrors hotwired/turbo, which does the submission work in a bubble listener. Adds an e2e regression: sending a chat message keeps you on the page and the message survives. Verified it fails on the reverted (capture) code. Closes #150
…epend the e2e on polling Self-review fixes. The onSubmit JSDoc still claimed "capture phase so we run before user @submit handlers", which is exactly backwards after the fix; rewrite it to describe the bubble ordering that makes the defaultPrevented guard work. Add a NOTE at the click registration that it stays capture and carries the same latent @click-preventDefault hijack, deferred to its own change (#153). Replace the chat e2e's fixed sleep with waitForFunction polling for the sentinel message so a slow WS broadcast cannot flake it (still times out, and so fails, on the buggy capture code). Refs #150
Collaborator
Author
Self-reviewLoop ran two rounds, last clean.
Verified: chat e2e passes with the fix and fails on the reverted (capture) code ("the sent chat message must remain visible"); node suite 1476; e2e 51; website / docs / ui-website boot 200 in dist mode. Follow-up: #153 tracks moving the |
vivek7405
added a commit
that referenced
this pull request
May 31, 2026
vivek7405
added a commit
that referenced
this pull request
May 31, 2026
vivek7405
added a commit
that referenced
this pull request
May 31, 2026
* chore: release @webjsdev/core 0.7.3 and @webjsdev/server 0.8.3 Release debt accumulated since the 0.7.2 / 0.8.2 release (#149): core 0.7.3 (patch): the two client-router fixes from #151 and #157 (JS-handled forms and links were hijacked by the router despite e.preventDefault). server 0.8.3 (patch): #161 (modulepreload hints no longer point at server-only or template-embedded files the auth gate 404s) and #156 (webjs check no longer leaks a git env var across worktrees). Both stay in a single minor line; all in-repo dependents pin ^0.7.0 / ^0.8.0, so no dependent range edits are needed. Lockfile regenerated. * chore: release @webjsdev/cli 0.10.0 (webjs vendor surface) cli 0.9.1 shipped without the `webjs vendor` command surface that #105 and #89 added (pin / unpin / list / audit / outdated / update, the --from multi-CDN selector, and --download caching). That is feature-level work, so a minor bump. create-webjs and webjsdev are thin shims that delegate to cli; widen their `@webjsdev/cli` range from ^0.9.0 to ^0.10.0 so the workspace keeps linking the local cli (a minor bump falls outside the old caret). They are not in the changelog auto-publish system, so they carry no changelog entry of their own. Lockfile regenerated.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #150.
In the demo blog, sending a live chat message sent the message but then scrolled
the page to the top and the just-sent message vanished. Same class of bug for
the live comments thread.
Root cause: the client router's
submitlistener was ondocumentin CAPTUREphase (since
e59f381, not a recent change), but a component's@submitisbound per-element and runs at-target, AFTER document capture. So for a
JS-handled form (chat/comments
e.preventDefault()and send over WebSocket /fetch), the router's
onSubmitran first; itsif (e.defaultPrevented) returnguard saw
false(the component had not run yet), so it intercepted the form:with no
actionit fell back to the current URL and navigated/swapped the page.The message was sent AND the page swapped, scrolling to top and wiping the
WebSocket-driven chat state.
Fix: register
submitin the BUBBLE phase (capture: false) so the component's@submitruns first and itspreventDefaultmakes the guard skip the form,honoring the documented "forms that preventDefault in @submit are untouched"
contract. Plain progressive-enhancement forms (no
@submit) still getintercepted and SPA-submitted. Click stays capture. Mirrors hotwired/turbo,
which performs the submission in a bubble listener.
Test plan
message survives. Verified it FAILS on the reverted (capture) code
("the sent chat message must remain visible") and passes with the fix.
npm testgreen (1476)ui-website boot 200 in dist mode
Note: the unit env (linkedom) does not model capture/bubble ordering of a
document-level vs element-level submit listener faithfully, so the regression
lives at the e2e layer where real event phases apply.
After merge
Needs a Railway redeploy to reach the live blog (the deployed services auto-deploy
on push to
main).