A person can keep typing after pressing save. If the earlier response arrives later, replacing the field with that returned value can erase newer work even when the server saved successfully.
I built a new independent state model rather than claiming that the previous demo already had autosave. Synthetic requests can be completed in a deliberately reversed order.
The draft is not the acknowledgement
edit changes the draft and its local revision; begin_save copies that revision and text into a request. A response never assigns text back to the draft. It acknowledges a particular captured edit rather than becoming a replacement for the current field. This distinction keeps a success handler from erasing work performed while it waited.
The input revision and server document version are separate values. Three local edits do not establish three committed server writes. Server versions in this experiment are explicit synthetic inputs. Real version assignment and conditional writes require a server contract such as the earlier transaction lab; this view model cannot establish them by itself.
Complete the second request first
Each request receives an increasing sequence. After the second success is applied, a response to the first request removes its own pending marker but cannot move the acknowledged sequence or server version backward. The test reverses responses for two captured drafts and checks the retained text and version, not merely a success boolean.
An unknown or already-consumed response identifier is rejected. That is an explicit model boundary, not a complete network duplicate-response recovery policy. The consuming interface must decide how to display or ignore a duplicate. Here the important evidence is that an unrecognized response is not silently associated with another request.
Preserve edits that were never sent
When someone edits again after sending a request, a success for the earlier revision must leave dirty true. acked_revision advances only to the captured request, and the latest local revision remains newer. Removing a pending indicator must not assert that every current keystroke has been saved. Failed requests do not advance acknowledgement.
The interface may need to distinguish pending work, a confirmed earlier revision and an unsent change. The functions return a dirty flag, pending count and server version; they do not implement localized status text or announcements. Defining those values and checking that people understand their presentation are separate verification tasks.
Client ordering cannot order server commits
Ignoring a late response cannot repair a server that actually overwrote a newer record with an older request. This module therefore does not solve all concurrent writes or duplicate saves. Expected-version checks, request identity and reconciliation with committed state remain server responsibilities, with their own transaction and authorization boundaries.
There is no login, durable pending queue, retry deadline or conflict merge. A newer successful response with a regressing server version is rejected, which detects an inconsistent claim without repairing storage. The implementation keeps current input separate from confirmed facts; its scope is deliberately smaller than an end-to-end save service.
Connect the contract deliberately
The package tests reversed responses, unsent edits, failure, repeated responses and regressing server versions. Functions copy state so before and after can be compared. These are local state tests and fixed example records, not browser tests that throttle a real connection or manipulate someone’s input.
For adoption, first find response handlers that replace draft fields. Connect an explicit server contract and status presentation, then exercise real input while requests are delayed. I share the process of breaking “saved” into distinct facts and leaving evidence for the facts this code actually checks. The package is reusable without importing any company records.