What we reproduced in our own bulk tool

Our bulk phone generator generates synchronously and has country, quantity and output-format controls. It has no seed control or background worker. At source commit e02631106672, changing any of those controls left the previous output visible and the previous records available for download. This is a local state mismatch, not evidence of a live asynchronous race.

We ran an offline DOM harness against the actual event handlers before and after the change. The three baseline edits left stale output visible. The revised handlers clear records, clear the textarea and hide the result on input, change or reset. Copy, JSON and CSV also compare the current option values with the generated batch's option values. A programmatic edit that emits no input event is caught at that final guard.

Measured actual-handler caseBeforeAfter
Country editPrevious output remainedCleared; regenerated export has current country
Quantity editPrevious output remainedCleared; regenerated export has current count
Format editPrevious output remainedCleared; regenerate before export
Copy / JSON / CSVNot scored in baselineSame current records
Programmatic country edit without eventNot scored in baselineCopy and export blocked
Form resetNot scored in baselinePrevious output cleared

The measured handler report states its scope: a minimal offline DOM harness, not a real-browser event-dispatch test. The generation ranges and output schema are unchanged. This fix does not certify that a structurally plausible generated value is unassigned or can receive SMS.

Why event handling and an export guard both matter

MDN's input-event reference distinguishes user edits from programmatic value assignments and form reset. The latter do not emit an ordinary input event. The change event is committed at different moments for different controls. We invalidate on input, change and reset, then recheck options at the action boundary. These are complementary checks.

For a synchronous generator, our option snapshot is enough to guard these observed edits. If options change away and back without an event and no asynchronous request exists, the existing batch again matches the options. An asynchronous producer needs a stronger definition of current.

The A → B → A case for an asynchronous producer

Imagine request A starts for US fixtures. The user switches to GB, then back to US and starts a new request. The new US request finishes first. The original US request finishes later. Comparing only country, quantity and seed would accept both, because the final fields equal the first request's fields. Our deliberately weak comparator reproduces that acceptance.

Our separate tutorial increments revision for every option transition and request for every generation attempt. Starting work invalidates the previously exportable batch. A completion is accepted only if both IDs still match. This also rejects the older of two requests with identical options. The example controls Promise fulfillment order manually; it makes no network requests and uses no timing guesses.

Promises do not provide a universal cancellation protocol. Even where the underlying operation supports cancellation, retain a commit guard so a completed obsolete result cannot become current.

Run our six offline state cases

node phone-fixture-export-state.mjs

Download the original standard-library JavaScript experiment and its measured report. We ran it on Node v24.19.0 on October 11, 2026.

CaseAsserted outcome
Region, quantity, seed edits (three cases)Old export throws; regenerated export has current options
A → B → A, old request finishes lastOld commit rejected; current export unchanged
Identical options, two requestsOlder request rejected
Captured serialized export, then new optionsCaptured two rows stay two; a new export is blocked until regeneration

The rows intentionally repeat fixed fictional US/GB examples from our source-pinned dataset. The seed is captured replay metadata only; this example implements no seeded random algorithm or uniqueness guarantee. The US example uses the limited NANPA fictional 555-0100–0199 interval, and the GB example uses Ofcom's drama mobile range. Delivery remains disabled. Do not substitute an arbitrary real phone number.

Inspect the committed export snapshot
{
  "revision": 3,
  "request": 2,
  "options": {
    "region": "US",
    "quantity": 2,
    "seed": 417
  },
  "rows": [
    {
      "id": 1,
      "region": "US",
      "phone": "+12025550123",
      "delivery_allowed": false
    },
    {
      "id": 2,
      "region": "US",
      "phone": "+12025550123",
      "delivery_allowed": false
    }
  ]
}

The state class checks row count, region and a false delivery flag. It assumes a trusted producer and genuine internal request tickets. It is not a phone parser, allocation lookup, provenance verifier or production-worker integration test. Validate those boundaries separately.

A small implementation checklist

  1. Capture all generation options, including dataset or formatting versions that affect output.
  2. Invalidate on every relevant transition, including reset; keep an action-time export guard.
  3. For asynchronous work, capture revision and request IDs and reject obsolete completions.
  4. Build the display and download from one committed snapshot; serialize before later edits.
  5. Test rapid changes, A → B → A, repeated Generate actions, reset and programmatic edits.

A matching snapshot and intact downloaded bytes answer different questions. Both checks can pass or fail independently.

FAQ from a late-result question

A public English developer question describes rapid filter edits and an older Promise overwriting a newer list. It is one question signal, not a frequency estimate or evidence of the author's location. Our own controlled experiment supplies the measured outcomes.

Why can my export contain the previous country's rows?

The controls and stored results may have separate lifetimes. Changing the controls does not necessarily regenerate or invalidate stored rows. Clear the old batch on edits and require a matching snapshot at export.

Why is comparing country values insufficient for asynchronous work?

A → B → A returns the fields to their original values while making the first request obsolete. Revision and request IDs distinguish the two A requests.

Does a local synchronous phone number generator need this check?

Yes, it can retain old output after an edit even without any asynchronous race. Our actual bulk-handler baseline reproduced that separate issue.

Does cancelling the old Promise solve every late result?

No universal Promise cancellation exists. Cancel the underlying operation where supported and still reject a result whose revision or request ID is obsolete.

Does the tutorial seed change the phone digits?

No. It is metadata in this state experiment. Fixed fictional rows isolate the export-state behavior; the example is not a seeded phone-number generation algorithm.

Original material: source-pinned before/after handler regression, six controlled state cases, one weak-comparator negative control, captured-export assertion, runnable JavaScript state class and original diagram. No calls or SMS.