Skip to content

Client router intercepts JS-handled forms (@submit preventDefault), navigating away #150

Description

@vivek7405

Problem

In the demo blog, sending a live chat message sends the message but then the
page scrolls to the top and the just-sent message disappears. Same class of bug
applies to the live comments thread.

Root cause

The client router's submit listener is registered on document in CAPTURE
phase (router-client.js enableClientRouter:
document.addEventListener('submit', onSubmit, true), from #e59f381). The
renderer binds a component's @submit handler per-element, which runs in the
at-target / bubble phase (render-client.js). So for a JS-handled form (the
chat/comments forms call e.preventDefault() in @submit and send via
WebSocket / fetch), the router's capture-phase onSubmit runs FIRST, before the
component's handler. Its guard if (e.defaultPrevented) return; therefore sees
false (the component has not run yet), so the router does NOT skip: it
resolves the action (these forms have no action, so it falls back to the
current URL), preventDefaults, and does performSubmission, navigating the
page. The component's @submit then also runs (sends the message). Net: the
message is sent AND the page navigates / swaps, scrolling to the top and wiping
the WebSocket-driven chat state.

The documented contract (AGENTS.md: "Forms that already e.preventDefault()
in @submit are untouched") is defeated by the capture-phase registration: the
defaultPrevented guard only works if the router runs AFTER the form's own
handler, i.e. in the BUBBLE phase. Mirrors hotwired/turbo, which does the
submission work in a bubble listener.

Fix

Register the submit listener in the bubble phase (capture false) in both
enableClientRouter and disableClientRouter, so a component's @submit
preventDefault is seen by the defaultPrevented guard and the router leaves
the form alone. Plain progressive-enhancement forms (no @submit) still get
intercepted and SPA-submitted, since nothing prevents their default.

Acceptance criteria

  • A form whose @submit calls preventDefault is NOT intercepted by the router (no navigation)
  • A plain <form action method> with no JS handler still SPA-submits via the router
  • Blog: sending a chat message keeps you on the page and the message stays; same for comments
  • Browser/e2e regression covering the chat (or a JS-handled form) send-and-stay path

Activity

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

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

  • Status
    Done

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions