Skip to content

fix: close #199 — remove Jaws.Replace and reject Replace/Remove broadcasts#211

Merged
linkdata merged 4 commits into
mainfrom
fix/remove-jaws-replace-199
Jul 22, 2026
Merged

fix: close #199 — remove Jaws.Replace and reject Replace/Remove broadcasts#211
linkdata merged 4 commits into
mainfrom
fix/remove-jaws-replace-199

Conversation

@linkdata

@linkdata linkdata commented Jul 22, 2026

Copy link
Copy Markdown
Owner

What

Closes the #199 DOM/server identity desync at the source, in two commits:

  1. Remove the Jaws.Replace(target, html) convenience helper and its two test callers.
  2. Reject what.Replace and what.Remove in Jaws.Broadcast — the public broadcast entry point — reporting the new sentinels ErrReplaceNotBroadcastable / ErrRemoveNotBroadcastable via reportMisuse and sending nothing.

Why

Jaws.Broadcast is public and handleBroadcast forwards unrecognized commands verbatim to every matching element. So removing the Jaws.Replace helper alone was not enough — a caller could still send wire.Message{What: what.Replace, ...} (or what.Remove) through raw Broadcast and reproduce the same strand: the DOM node is mutated/removed while the server-side Element stays registered with no reachable DOM node.

Both commands mutate a specific element's node in a way a broadcast cannot keep in sync with the server-side registry:

  • Replace swaps the whole node for HTML that must carry the element's own id; one broadcast payload cannot preserve the distinct id of every matched element.
  • Remove deletes a child node and requires the child's Element to be unregistered server-side. The broadcast default path never calls DeleteElement, and the client acks only the child's descendants, never the child itself (jawsRemoving uses querySelectorAll, which excludes the node being removed) — so the child Element is stranded. This is precisely why the safe per-element Element.Remove calls Request.DeleteElement(child) itself.

Safe forms (unaffected)

  • Element.Replace — per-element, validates the replacement carries the element's id.
  • Element.Remove — per-element child removal, unregisters the child server-side.
  • Jaws.Delete — broadcast-level element removal that stays in sync (its handleBroadcast case calls DeleteElement).
  • The what.Replace / what.Remove wire commands and all client-side handling are untouched.

Breaking change

Removes the exported Jaws.Replace method (API break for any external caller; nothing in-repo used it outside tests). Jaws.Broadcast now rejects what.Replace / what.Remove messages instead of forwarding them — these were never emitted by any in-repo helper.

Verification

  • go build ./..., go vet ./... — clean
  • go test -race ./... — all packages pass
  • gofumpt (touched files), staticcheck ./..., golangci-lint run ./... — clean / 0 issues
  • TestBroadcast_RejectsElementMutatingCommands — table-driven regression test exercising raw Jaws.Broadcast for both Replace and Remove, asserting the sentinel is reported and nothing reaches bcastCh (covers both the debug-panic and production-log branches).

Noted, deliberately out of scope

  • Broadcasting what.SAttr/what.RAttr with id is refused only client-side on the raw-broadcast path (the server-side ErrReservedAttribute guard lives in the attribute helpers). This does not strand — the id-bearing node survives — so it is not a Jaws.Replace leaves live server Elements without a DOM identity #199-class defect. Happy to follow up separately if you want the server-side guard extended to the broadcast path.

linkdata added 2 commits July 22, 2026 20:02
Jaws.Replace broadcast a what.Replace frame that swapped each matching DOM
node for the supplied HTML but left the corresponding server-side Element
registered. The replacement HTML normally carried no JaWS id attribute, so
after replacement the Element stayed registered with no matching browser id
and later commands targeting the same tag failed to locate their element.

Element.Replace requires the replacement HTML to carry the element's own id=
(validated), so it preserves DOM identity by construction. The broadcast form
enforced no such contract and was a footgun, so remove it along with its two
test callers.

Element.Replace, the what.Replace wire command, and the client-side Replace
handling are unaffected.
Removing the Jaws.Replace convenience helper did not close the underlying
identity desync: Jaws.Broadcast is public and handleBroadcast forwards
unrecognized commands verbatim to matching elements, so a caller could still
send wire.Message{What: what.Replace, ...} (or what.Remove) through raw
Broadcast and strand the server-side Element with no reachable DOM node.

Reject both what.Replace and what.Remove in Jaws.Broadcast, reporting
ErrReplaceNotBroadcastable / ErrRemoveNotBroadcastable via reportMisuse and
sending nothing.

  - Replace swaps the whole node for HTML that must carry the element's own id,
    and a single broadcast payload cannot preserve the distinct id of every
    matched element. Element.Replace is the safe per-element form; it validates
    that the replacement carries the id.
  - Remove deletes a child node and requires the child's Element to be
    unregistered server-side. The broadcast default path never calls
    DeleteElement, and the client acks only the child's descendants, never the
    child itself, so the child Element is stranded. Element.Remove is the safe
    per-element form (it calls Request.DeleteElement) and Jaws.Delete is the
    safe broadcast-level element removal (it stays in sync).

Element.Replace, Element.Remove, Jaws.Delete, the what.Replace and what.Remove
wire commands, and the client-side handling are unaffected. A table-driven
regression test exercises raw Jaws.Broadcast for both commands.
@linkdata linkdata changed the title fix: remove the Jaws.Replace broadcast method (#199) fix: close #199 — remove Jaws.Replace and reject Replace/Remove broadcasts Jul 22, 2026
The [Element]s doc link rendered literally because the trailing s made it
an invalid doc-link target. Rephrase as "matched [Element] values" so it
links.
@linkdata linkdata closed this Jul 22, 2026
@linkdata linkdata reopened this Jul 22, 2026
@linkdata
linkdata merged commit 1e0d9db into main Jul 22, 2026
7 checks passed
@linkdata
linkdata deleted the fix/remove-jaws-replace-199 branch July 22, 2026 20:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant