fix: MPS-24 Phase 2.8e — form.action DOM-clobbered by <input name=action>

With 2.8d live the operator's Network panel proved the proxy-proof
ajax=1 signal works (real fetch to /tags -> 200, 0.7kB JSON) but also
showed 4 requests to a URL literally named [object HTMLInputElement],
with a CORRECT payload (action=delete, tag_slug=..., ajax=1).

Cause: every tag form contains <input type=hidden name=action>. A
named form control clobbers the built-in HTMLFormElement.action
property (DOM clobbering), so fetch(form.action) fetched that <input>
element -> 'String([object HTMLInputElement])' -> resolved to the shop
page (200 HTML, 25.9kB) -> reportFailure, no DOM change ('closer but
nothing changes on screen').

Fix: read form.getAttribute('action') (content attribute, never
clobbered) in submitForm + doReorder; build the programmatic toggle
form with setAttribute('action', ...) instead of form.action =.
No bare form.action reads remain. product_tags.js unaffected (posts
to data-product-tags-url). node --check clean; JS-only defect fix,
no Python/template/test impact.

This closes the chain: 2.8c stale cache -> 2.8d proxy-stripped
X-Requested-With -> 2.8e clobbered form.action. Docs: mps-24.md
Phase 2.8e, CLAUDE.md DOM-clobbering note.
This commit is contained in:
russell@unturf.com 2026-05-16 10:32:50 -04:00
parent 8e31124ca8
commit 7836035d1a
No known key found for this signature in database
3 changed files with 41 additions and 4 deletions

View file

@ -363,6 +363,25 @@ Tests (`test_functional.py::TestProductTagsSpa`):
Deferred (occasional click, not the hot path): AJAX-ifying the
"Suggest categories" link — still a full navigation by design.
**Phase 2.8e — the LAST link: `form.action` DOM-clobbered** (shipped
2026-05-16): with 2.8d live, the operator's Network panel showed the
proxy-proof `ajax=1` working (a real `fetch` to `tags` → 200, 0.7 kB
JSON) — but also four requests to a URL literally named
`[object HTMLInputElement]`, and the Payload tab confirmed the body
was correct (`action=delete`, `tag_slug=…`, `ajax=1`). Cause: every
tag form contains `<input type="hidden" name="action">`. A named form
control **clobbers the built-in `HTMLFormElement.action` property**
(DOM clobbering / named-property override), so `fetch(form.action)`
fetched that `<input>` element — coerced to the string
`"[object HTMLInputElement]"` → resolved against the shop, returned
the 25.9 kB shop page (200, non-JSON) → `reportFailure` (no DOM
change: "closer but nothing changes"). Fix: read
`form.getAttribute("action")` (content attribute, never clobbered),
never `form.action`; build programmatic forms with
`setAttribute("action", …)`. tag_bulk.js: submitForm, doReorder,
swapToggleForm. (No browser test harness exists; guarded via
CLAUDE.md note + the `ajax=1` server tests from 2.8d.)
**Phase 2.8d — the OTHER root cause: proxy strips X-Requested-With**
(shipped 2026-05-16): even after 2.8c (fresh JS confirmed loading,
`tag_bulk.js?v=<hash>` 200 in the operator's Network panel),