Retrying after a missing save response is reasonable. The server still needs a contract distinguishing another attempt at the same action from a new action. I inspected actual API source and tests, then made an anonymous in-memory example to explain that contract.
A missing response does not prove that nothing was saved
When a save screen keeps waiting, pressing again feels reasonable. An AI operator can also retry after a timeout. If the first request already committed on the server, a second request may create another post. I read actual replay tests and their handler to establish a distinction between one intended user action and several transport attempts.
I ran five original local checks covering identical requests, changed bodies, different principals, concurrent attempts and replay after key revocation. They passed with an in-memory repository. That does not verify duplicate prevention across distributed servers and durable databases. The published explanation retains the boundary actually reproduced by those checks.
Define the scope of the intended operation
Comparing only a request key can mix different users who happen to choose the same value. The original handler scoped the key by authenticated principal and operation. The new module likewise combines principal, operation and request key. The principal should come from successful authorization rather than a user name accepted blindly from an input field.
Matching scope, key and body returns the recorded result. A changed body within the same key conflicts instead of creating another entry. The new example normalizes ordinary object key order while preserving array order. It does not hide changes that alter the stored meaning. Files and special object types are outside this deliberately plain JSON contract.
Record an in-flight request before its work starts
If a request is recorded only after its write finishes, another arrival can begin the same work in the meantime. The new example installs the deferred commit Promise first. Matching concurrent requests wait for that same Promise. Its check verifies that three attempts receive one result while the commit count remains one.
This mechanism belongs to one JavaScript instance. Different processes cannot see each other’s Map, and restarting loses the ledger. Production requires durable uniqueness for principal, operation and key, coordinated with the data transaction. A local success should not be described as a worldwide exactly-once storage guarantee. The example makes the coordination requirement visible rather than replacing it.
Explore the implementation
In-memory replay ledger with authorization first
function canonicalBody(value,depth=0){
if(depth>20)throw new TypeError('Body is too deeply nested');
if(value===null||typeof value==='string'||typeof value==='boolean')return JSON.stringify(value);
if(typeof value==='number'&&Number.isFinite(value))return JSON.stringify(value);
if(Array.isArray(value))return '['+value.map(x=>canonicalBody(x,depth+1)).join(',')+']';
if(value&&Object.getPrototypeOf(value)===Object.prototype)return '{'+Object.keys(value).sort().map(k=>JSON.stringify(k)+':'+canonicalBody(value[k],depth+1)).join(',')+'}';
throw new TypeError('Plain JSON body required');
}
export function createRequestLedger({authorize,commit,maxEntries=1000}){
if(typeof authorize!=='function'||typeof commit!=='function'||!Number.isInteger(maxEntries)||maxEntries<1)throw new TypeError('Explicit authorization, commit and capacity required');
const entries=new Map();
return async function request({actor,operation,key,body}){
// Authorization is repeated before reading even a successful cached response.
const principal=await authorize(actor,operation);
if(typeof principal!=='string'||!principal||principal.length>128||typeof operation!=='string'||!operation||operation.length>128||typeof key!=='string'||! /^[A-Za-z0-9_-]{8,128}$/.test(key))throw new TypeError('Principal, operation and bounded request key required');
const fingerprint=canonicalBody(body);if(fingerprint.length>100000)throw new RangeError('Example body is too large');
const identity=JSON.stringify([principal,operation,key]),previous=entries.get(identity);
if(previous){if(previous.fingerprint!==fingerprint){const error=new Error('Same request key has different content');error.status=409;throw error;}return JSON.parse(await previous.promise);}
if(entries.size>=maxEntries)throw new RangeError('Example ledger capacity reached');
// Install the promise before the deferred commit can begin.
const promise=Promise.resolve().then(()=>commit({principal,operation,body:JSON.parse(fingerprint)})).then(result=>canonicalBody(result));
entries.set(identity,{fingerprint,promise});
// Failed operations stay recorded: their side effect may have already happened.
return JSON.parse(await promise);
};
}
Do not discard an uncertain failed outcome casually
A failed response does not establish that the write never happened. Failure may occur while constructing the response after a side effect. The new ledger retains a rejected Promise so an immediate retry with the same key does not casually repeat that effect. Such uncertainty needs reconciliation with actual stored state and an explicit recovery procedure.
The example has a capacity limit and rejects new entries when full. It does not invent an automatic retention or failed-result correction policy. A production store needs those rules based on the operation’s meaning. Generating a fresh key and resending should likewise follow a decision about whether this is actually a new intended action, not merely a way to bypass an unresolved outcome.
Recheck authorization before returning a previous success
Reading the replay cache before authorization can expose an earlier result through a revoked key. The original source authenticated and checked operation permission before consulting its ledger. The new example calls authorize on every attempt. A past success is not permission for a current request.
Nine checks of the new module cover concurrency, conflicts, revocation, uncertain failures and mutation of returned values. The screen below is an illustrative temporary-memory simulation; it changes no real posts or administrator keys. New rendered browser interaction remains unverified. Readers can reuse the decision boundaries while continuing verification against their actual durable store and authorization system.