Issue 159 Defaked XHR Sync Implementation Plan

For Claude: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.

Goal: Make defaked FakeXMLHttpRequest instances propagate post-open() request-side property changes to the underlying native XHR, fixing issue #159 without relying on Proxy.

Architecture: Keep the current defake model, where open() swaps selected methods over to a real XHR, but replace the one-off responseType sync in send() with a small shared request-state sync helper inside FakeXMLHttpRequest.defake. Start with an audit of mutable request-side properties and defaked methods, then use that audit to define a narrow sync list for the helper and apply it before send() and any other defaked methods that need current fake-side state.

Tech Stack: Node.js, Mocha, Referee, Sinon, nise fake XHR implementation


Task 1: Audit defaked request-side state and method usage

Files: - Modify: docs/plans/2026-03-03-issue-159-defaked-xhr-sync.md - Review: lib/fake-xhr/index.js:323-380 - Review: lib/fake-xhr/index.js:589-610 - Review: lib/fake-xhr/index.js:760-804 - Review: lib/fake-xhr/index.test.js:2334-2450

Step 1: Inspect defake control flow

Review how open() enters FakeXMLHttpRequest.defake() when filters match, and list which methods are redirected to the working XHR.

Step 2: Enumerate mutable fake-side request properties

Inspect fake XHR initialization and request flow to identify request-side properties that can change after open() and could affect the native XHR. At minimum, audit:

[
    "responseType",
    "withCredentials",
    "timeout",
]

Also explicitly note why properties such as upload, event handlers, method, url, username, password, requestHeaders, and response-side fields should remain outside this sync helper unless the audit proves otherwise.

Step 3: Decide which defaked methods need pre-call sync

For each redirected method below, record whether it should sync request-side state before delegation:

[
    "open",
    "setRequestHeader",
    "abort",
    "getResponseHeader",
    "getAllResponseHeaders",
    "addEventListener",
    "overrideMimeType",
    "removeEventListener",
    "send",
]

Expected outcome: the audit should justify option 2, with a shared helper used at least by send(), and document whether any additional method needs the same sync now instead of leaving that as guesswork.

Task 2: Lock in the bug with focused defake tests

Files: - Modify: lib/fake-xhr/index.test.js:2404-2450 - Test: lib/fake-xhr/index.test.js

Step 1: Write the failing test for withCredentials

Add a defake-focused test next to the existing responseType coverage that:

it("passes withCredentials to working XHR object on send", function () {
    var workingXHRInstance;
    var workingXHROverride = function () {
        workingXHRInstance = this;
        this.withCredentials = false;
        this.open = function () {};
        this.send = function () {};
    };

    runWithWorkingXHROveride(workingXHROverride, function () {
        FakeXMLHttpRequest.defake(fakeXhr, []);
        fakeXhr.withCredentials = true;
        fakeXhr.send();
        assert.equals(workingXHRInstance.withCredentials, true);
    });
});

Step 2: Write the failing test for timeout if the audit keeps it in scope

If Task 1 concludes that timeout should sync, add a matching test asserting a post-open() timeout mutation reaches the working XHR before send().

Step 3: Run targeted tests to verify they fail

Run: npm test -- --grep "passes .* to working XHR object" Expected: FAIL for the new withCredentials case on the current implementation, and FAIL for timeout only if you added that test.

Task 3: Implement the shared request-state sync helper

Files: - Modify: lib/fake-xhr/index.js:323-347 - Test: lib/fake-xhr/index.test.js

Step 1: Replace the special-case responseType sync with a helper

Inside FakeXMLHttpRequest.defake(), introduce a helper that copies a narrow audited list of request-side properties from fakeXhr to xhr only when the values differ.

var syncRequestState = function (attrs) {
    attrs.forEach(function (attr) {
        if (xhr[attr] !== fakeXhr[attr]) {
            xhr[attr] = fakeXhr[attr];
        }
    });
};

Use the audit result to drive the exact attribute list. Start from:

["responseType", "withCredentials", "timeout"]

and remove any property the audit shows is unsafe or ineffective to sync.

Step 2: Call the helper before delegating send()

Update the defaked send() implementation to call the helper before apply(xhr, "send", arguments).

Step 3: Apply sync to any additional defaked method justified by the audit

If Task 1 identifies another method that depends on current fake-side request state, call the same helper there. Otherwise, keep the change limited to send() and record that this is an intentional narrow fix.

Step 4: Preserve the existing synchronous XHR guard

Ensure the current issue #61 behavior remains intact: when synchronous requests expose a guarded responseType setter, the helper must not introduce extra setter calls beyond the existing contract.

Task 4: Verify regressions around defaked and filtered XHR behavior

Files: - Test: lib/fake-xhr/index.test.js

Step 1: Run the targeted defake block

Run: npm test -- --grep "defake" Expected: PASS

Step 2: Run filtered defake coverage

Run: npm test -- --grep "defaked XHR filters" Expected: PASS

Step 3: Run the full fake XHR suite

Run: npm test -- lib/fake-xhr/index.test.js Expected: PASS

Task 5: Final verification and branch hygiene

Files: - Modify: lib/fake-xhr/index.js - Modify: lib/fake-xhr/index.test.js

Step 1: Run lint if implementation changed shape materially

Run: npm run lint Expected: PASS

Step 2: Summarize the audit in the final PR description or commit message

Document:

- why Proxy was rejected
- which request-side properties were audited
- which ones were synced
- which defaked methods were evaluated
- why the final sync hook is limited to the chosen methods

Step 3: Commit

git add lib/fake-xhr/index.js lib/fake-xhr/index.test.js docs/plans/2026-03-03-issue-159-defaked-xhr-sync.md
git commit -m "plan: add issue 159 defaked xhr sync plan"