HomeJournalThis post

Build conflict-aware autosave with ETags

ETag preconditions turn silent overwrites into an explicit autosave conflict state with preserved drafts, comparison, merge, reload, and retry.

JP
JP Casabianca
Designer/Engineer · Bogotá

Autosave can lose work more quietly than a Save button.

Two tabs load version A. One saves version B. The other autosaves its stale copy and returns the record to a shape based on A. Every request succeeded; the user's newer work disappeared.

RFC 9110 defines If-Match specifically for conditional state-changing requests and preventing lost updates. A false precondition returns 412 instead of performing the method.

We will store the ETag received with the document, send it on every autosave, and turn a mismatch into a reviewable conflict rather than an invisible overwrite.

Autosave is a concurrency feature before it is a convenience feature.

01 · LoadReceive document and validator

Keep the server representation, editable draft, and strong ETag that describes the version the draft began from.

02 · SaveSend If-Match

Patch changed fields only when the server still holds the observed representation; update the validator on success.

03 · ResolveHandle 412 explicitly

Fetch current state, compare base/draft/server, preserve user work, and choose merge, overwrite with authority, or discard.

Figure 1: The ETag connects loaded state to the save precondition.

Separate base, draft, and server state

The client needs the last accepted server base, the current local draft, and any newly fetched server version to reason about conflict.

I would pressure-test that decision with four questions:

  • What did editing begin from?
  • What changed locally?
  • What has the server accepted?
  • Can all three versions be preserved?

The failure mode here is using one mutable object for loaded, editing, and saved state. In editors and settings surfaces where multiple tabs, teammates, background jobs, stale caches, intermittent networks, and optimistic UI can silently replace newer server state, that can hide the exact boundary a reviewer or teammate needs to understand. My working artifact would be a three-version client state model. I want it close enough to the implementation that it can change the work, not created afterward to decorate the story.

The result I would look for is enough information for safe reconciliation. That is a narrower claim than saying the whole system improved, but it is also one I can verify and defend.

In practice, I would put a three-version client state model beside the question “What did editing begin from?” before the first implementation review. The next pass would use “What changed locally?” to test the boundary, then “What has the server accepted?” to expose the state most likely to be missed. I would keep “Can all three versions be preserved?” for the release check because it asks whether the decision still holds outside the ideal path. The work is ready to move when the artifact can explain the choice and the observed result supports enough information for safe reconciliation.

Return a strong validator

The API should emit a validator that changes whenever the representation relevant to editing changes.

The practical review starts here:

  • What fields affect the ETag?
  • Is the validator strong?
  • Can different encodings vary?
  • Does every successful mutation return the new value?

Those questions keep hashing only convenient fields while hidden edits remain overwriteable from becoming the default. I would capture the decision in an ETag generation contract, then use it while the work is still cheap to change. For loss-aware collaborative editing, the artifact should make ownership, constraint, and next action visible without requiring a private explanation.

Success would look like a validator aligned with edit conflict. If I cannot point to that evidence, I have a direction, not a finished decision.

The implementation move is to make an ETag generation contract part of the working surface. I would use it to answer “What fields affect the ETag?” while scope is still flexible, and “Is the validator strong?” before code or content becomes expensive to unwind. During QA, “Can different encodings vary?” and “Does every successful mutation return the new value?” become concrete checks rather than discussion prompts. That sequence turns loss-aware collaborative editing into something the team can operate and gives me a specific outcome to report: a validator aligned with edit conflict.

  1. DirtyLocal work changed

    Debounce is pending or a save is in flight; leaving may still risk work depending on local persistence.

  2. SavingConditional request active

    The submitted revision and draft revision are distinct so newer typing is not marked saved by an older response.

  3. ConflictServer changed first

    Editing remains available, both versions are preserved, and the product asks for a bounded reconciliation decision.

Figure 2: Autosave needs more than saved and unsaved.

Track dirty fields explicitly

Field-level changes reduce payloads and make conflict comparison more meaningful than replacing an entire record on every keystroke.

Before implementation, I would answer:

  • Which field changed?
  • Can normalization alter it?
  • Are deletes represented?
  • Which derived fields stay server-owned?

The artifact is a typed patch and dirty-field set. Its job is to expose the tradeoff early enough that design, engineering, support, or product can disagree with something concrete. The common trap is serializing the whole stale document for every save; it moves uncertainty downstream and makes the final interface carry a problem the system never resolved.

For me, the useful receipt is narrower writes and clearer diffs. That connects an autosave loop that sends the last observed entity tag through If-Match and treats 412 as a product conflict state to an observable result instead of a process claim.

I would test this with one typical case and one boundary case. The typical case should make “Which field changed?” easy to answer. The boundary should force a decision about “Can normalization alter it?” and “Are deletes represented?.” I would record both in a typed patch and dirty-field set, including the part that stayed unresolved after the first pass. The final check, “Which derived fields stay server-owned?,” is where the artifact earns its place: it either supports narrower writes and clearer diffs, or it shows exactly why another iteration is needed.

Debounce without losing revision identity

Each scheduled or active save needs the local draft revision it represents so a late response cannot mark newer typing as saved.

I would use these prompts during the working review:

  • Which draft revision is submitted?
  • Can the request be aborted?
  • What if typing continues?
  • When does saved become true?

If the team slips into setting saved on any successful response, the product can still look complete while its operating rule stays ambiguous. I would make a monotonic local save revision the shared reference and keep it small enough to update as evidence changes.

The standard is accurate save status under rapid editing. That tells me whether the decision helped the product, not merely whether the document was completed.

The working sequence is small: draft a monotonic local save revision, review it against “Which draft revision is submitted?,” implement the narrowest useful path, and then return with evidence for “Can the request be aborted?.” I would use “What if typing continues?” to inspect product consequence and “When does saved become true?” to decide whether the result is stable enough to ship. This keeps setting saved on any successful response visible as a known risk and makes accurate save status under rapid editing the release receipt rather than a hopeful conclusion.

SignalDecisionWorking note
IndependentMerge automaticallyDifferent fields or append-only values can combine when validation proves no semantic collision.
OverlappingAsk with contextThe same field changed in both versions; show base, local, and server values with responsible authorship when available.
ConsequentialRequire reviewPermissions, prices, legal text, or external actions should not be overwritten through a generic keep mine button.
Figure 3: Conflict resolution follows field meaning.

Send If-Match on mutations

Every update and delete based on loaded state should carry the observed ETag and let the server reject stale intent before applying it.

I would pressure-test that decision with four questions:

  • Which methods are conditional?
  • Is missing If-Match allowed?
  • What status reports mismatch?
  • Can clients bypass the rule?

The failure mode here is treating conditional requests as an optional frontend feature. In editors and settings surfaces where multiple tabs, teammates, background jobs, stale caches, intermittent networks, and optimistic UI can silently replace newer server state, that can hide the exact boundary a reviewer or teammate needs to understand. My working artifact would be a required precondition middleware. I want it close enough to the implementation that it can change the work, not created afterward to decorate the story.

The result I would look for is server-enforced protection from lost updates. That is a narrower claim than saying the whole system improved, but it is also one I can verify and defend.

In practice, I would put a required precondition middleware beside the question “Which methods are conditional?” before the first implementation review. The next pass would use “Is missing If-Match allowed?” to test the boundary, then “What status reports mismatch?” to expose the state most likely to be missed. I would keep “Can clients bypass the rule?” for the release check because it asks whether the decision still holds outside the ideal path. The work is ready to move when the artifact can explain the choice and the observed result supports server-enforced protection from lost updates.

Treat 412 as product state

A precondition failure is expected concurrency, so the UI should preserve editing and explain that a newer server version exists.

The practical review starts here:

  • Is local work still safe?
  • Who or what changed the server?
  • Can the user keep editing?
  • What next action is available?

Those questions keep showing a generic network error or silently retrying from becoming the default. I would capture the decision in a visible conflict state with both versions, then use it while the work is still cheap to change. For loss-aware collaborative editing, the artifact should make ownership, constraint, and next action visible without requiring a private explanation.

Success would look like truthful recovery without discarded work. If I cannot point to that evidence, I have a direction, not a finished decision.

The implementation move is to make a visible conflict state with both versions part of the working surface. I would use it to answer “Is local work still safe?” while scope is still flexible, and “Who or what changed the server?” before code or content becomes expensive to unwind. During QA, “Can the user keep editing?” and “What next action is available?” become concrete checks rather than discussion prompts. That sequence turns loss-aware collaborative editing into something the team can operate and gives me a specific outcome to report: truthful recovery without discarded work.

Build a three-way diff

Comparing base-to-local and base-to-server reveals independent changes that can merge and overlapping changes that need judgment.

Before implementation, I would answer:

  • Which fields changed on each side?
  • Are arrays order-sensitive?
  • Can rich text merge?
  • What context makes a difference consequential?

The artifact is a field-aware three-way diff. Its job is to expose the tradeoff early enough that design, engineering, support, or product can disagree with something concrete. The common trap is comparing only local and current values; it moves uncertainty downstream and makes the final interface carry a problem the system never resolved.

For me, the useful receipt is conflict information that explains causality. That connects an autosave loop that sends the last observed entity tag through If-Match and treats 412 as a product conflict state to an observable result instead of a process claim.

I would test this with one typical case and one boundary case. The typical case should make “Which fields changed on each side?” easy to answer. The boundary should force a decision about “Are arrays order-sensitive?” and “Can rich text merge?.” I would record both in a field-aware three-way diff, including the part that stayed unresolved after the first pass. The final check, “What context makes a difference consequential?,” is where the artifact earns its place: it either supports conflict information that explains causality, or it shows exactly why another iteration is needed.

Bound overwrite authority

Keep mine may be reasonable for personal notes but dangerous for permissions, money, shared configuration, or externally consumed content.

I would use these prompts during the working review:

  • Who may force overwrite?
  • Which fields prohibit it?
  • Is reason captured?
  • What audit event is written?

If the team slips into offering unconditional overwrite for every entity, the product can still look complete while its operating rule stays ambiguous. I would make a consequence-based conflict policy the shared reference and keep it small enough to update as evidence changes.

The standard is reconciliation proportional to impact. That tells me whether the decision helped the product, not merely whether the document was completed.

The working sequence is small: draft a consequence-based conflict policy, review it against “Who may force overwrite?,” implement the narrowest useful path, and then return with evidence for “Which fields prohibit it?.” I would use “Is reason captured?” to inspect product consequence and “What audit event is written?” to decide whether the result is stable enough to ship. This keeps offering unconditional overwrite for every entity visible as a known risk and makes reconciliation proportional to impact the release receipt rather than a hopeful conclusion.

Add offline draft persistence

Local persistence should protect unsent work while clearly distinguishing device-saved drafts from server-accepted versions.

I would pressure-test that decision with four questions:

  • Where is the draft stored?
  • How is sensitive content handled?
  • When does it expire?
  • How is restored state labeled?

The failure mode here is calling local persistence saved. In editors and settings surfaces where multiple tabs, teammates, background jobs, stale caches, intermittent networks, and optimistic UI can silently replace newer server state, that can hide the exact boundary a reviewer or teammate needs to understand. My working artifact would be an offline draft lifecycle. I want it close enough to the implementation that it can change the work, not created afterward to decorate the story.

The result I would look for is recoverable work without false server confidence. That is a narrower claim than saying the whole system improved, but it is also one I can verify and defend.

In practice, I would put an offline draft lifecycle beside the question “Where is the draft stored?” before the first implementation review. The next pass would use “How is sensitive content handled?” to test the boundary, then “When does it expire?” to expose the state most likely to be missed. I would keep “How is restored state labeled?” for the release check because it asks whether the decision still holds outside the ideal path. The work is ready to move when the artifact can explain the choice and the observed result supports recoverable work without false server confidence.

Test real concurrency

Use two clients, reordered responses, offline transitions, aborts, server normalization, deletes, and high-consequence fields to verify no update disappears silently.

The practical review starts here:

  • Can two tabs race?
  • Can responses arrive out of order?
  • Does 412 preserve the draft?
  • Is every overwrite explicit?

Those questions keep testing autosave with one client and a fast network from becoming the default. I would capture the decision in a deterministic concurrency test suite, then use it while the work is still cheap to change. For loss-aware collaborative editing, the artifact should make ownership, constraint, and next action visible without requiring a private explanation.

Success would look like evidence that convenience does not erase work. If I cannot point to that evidence, I have a direction, not a finished decision.

The implementation move is to make a deterministic concurrency test suite part of the working surface. I would use it to answer “Can two tabs race?” while scope is still flexible, and “Can responses arrive out of order?” before code or content becomes expensive to unwind. During QA, “Does 412 preserve the draft?” and “Is every overwrite explicit?” become concrete checks rather than discussion prompts. That sequence turns loss-aware collaborative editing into something the team can operate and gives me a specific outcome to report: evidence that convenience does not erase work.

What I would show in the work

The public version needs evidence from the work itself. For this topic, the first five artifacts I would reach for are:

  • a three-version client state model
  • an ETag generation contract
  • a typed patch and dirty-field set
  • a monotonic local save revision
  • a required precondition middleware

I would not publish all five at equal weight. One should orient the reader, one should reveal the hardest tradeoff, and one should prove the result. The others can live in a downloadable note or appear as supporting frames. That edit matters because an autosave loop that sends the last observed entity tag through If-Match and treats 412 as a product conflict state becomes harder to understand when every process detail is treated as equally important.

I would also show one rejected direction. The useful version is specific: which option looked attractive, which constraint made it wrong, and what evidence supported the narrower choice. That gives an engineering manager something real to question and keeps the case study from reading like the final answer was obvious from the beginning.

conflict-aware-autosave.ts
# load
GET /docs/42 → ETag: rev-17
Store base rev-17 and editable draft; changes advance a local draft revision independently.

# save PATCH If-Match: rev-17 Server applies patch only if current ETag matches, returns rev-18, and client associates response with submitted draft revision.

# conflict 412 + current rev-19 Fetch or include current representation; construct three-way diff; never clear local draft before user resolves.

Figure 4: The request sequence makes stale writes impossible by default.

Resource path

The practical follow-up I would build is a conflict-aware autosave starter with dirty tracking, debounce, AbortController, ETag storage, If-Match mutation, 412 handling, field diff, merge choices, retry, offline state, and concurrency tests. I am treating that as a resource backlog item, not pretending the adjacent downloads below are the same artifact. The related cards cover useful pieces of the workflow today; this specific file should only be published when its examples, fields, and instructions are complete.

The first version should stay concise: context, constraint, decision, evidence, owner, and follow-up. Its value would come from helping someone repeat this exact review, not from adding another generic PDF to the site.

Review checklist

The article-specific review questions are:

  • What did editing begin from?
  • What fields affect the ETag?
  • Which field changed?
  • Which draft revision is submitted?
  • Which methods are conditional?
  • Is local work still safe?
  • Which fields changed on each side?
  • Who may force overwrite?
  • Where is the draft stored?
  • Can two tabs race?

I would add two editorial checks before publishing: can a recruiter find the point in the first minute, and can an engineer trace at least one claim to an implementation or production receipt? If either answer is no, the article needs another edit.

Implementation notes

For loss-aware collaborative editing, I would write the implementation note before polish. It would name the changed surface, source of truth, owner, failure boundary, and verification path. Those details prevent the principle from floating above the actual code or operational workflow.

The proof signals I care about are specific to this article:

  • truthful recovery without discarded work
  • conflict information that explains causality
  • reconciliation proportional to impact
  • recoverable work without false server confidence
  • evidence that convenience does not erase work

I would choose two or three of those signals for the first release rather than instrumenting everything. The strongest pair usually combines one direct behavior check with one operating check: a route and a data query, a keyboard path and a support state, a handler replay and a reconciliation result, or a migration count and a rendered screen.

The follow-up belongs in the note before shipping. It should say what remains temporary, what evidence would trigger another pass, and who owns that decision. That is how the first version stays intentionally narrow without making the boundary invisible.

Case-study packaging

I would structure the case-study version around the four visual lessons already established:

  • The ETag connects loaded state to the save precondition.
  • Autosave needs more than saved and unsaved.
  • Conflict resolution follows field meaning.
  • The request sequence makes stale writes impossible by default.

The opening frame explains the product pressure. The middle two show the decision moving through the system. The last frame is the receipt: what was checked, what held, and what remained unresolved. That order lets the reader move from product judgment into implementation detail without reconstructing the whole project first.

I would include one caveat tied to editors and settings surfaces where multiple tabs, teammates, background jobs, stale caches, intermittent networks, and optimistic UI can silently replace newer server state: a data limit, rollout boundary, unsupported state, external dependency, or result that is still directional. A precise caveat makes the evidence easier to trust because it shows where the claim stops.

The final test is whether the page creates a better conversation. If the artifact helps someone ask a sharper question about product judgment, implementation detail, or release proof in a live interview, it belongs in the story.

Interview angle

In an interview, I would explain this through an autosave loop that sends the last observed entity tag through If-Match and treats 412 as a product conflict state. The story should start with the product pressure, then move into the system constraint, the artifact, and the proof. That order keeps the answer grounded. It also gives the interviewer several places to go deeper: data, frontend architecture, design systems, support, migration, accessibility, or release process.

The strongest version of the answer includes a tradeoff. I want to be able to say what I chose, what I left alone, and how I knew the work helped. That is more credible than presenting every project as a clean win.

The hiring signal

Conflict-aware autosave is a hiring signal because it shows I can connect HTTP preconditions, client state, concurrency, and humane recovery instead of equating frequent requests with safety.

That is the level I want this site to communicate. The work should show taste, but it should also show operating judgment. It should make me look like someone who can enter a real product system, understand the messy middle, ship the useful version, and leave enough proof for the next person to trust it.

Companion artifacts

Use this after reading.

Practical downloads and templates that turn the article into something you can bring into a product review, implementation pass, or agent workflow.

DownloadJun 2026

Front-End State Recipes

Reusable recipes for optimistic actions, loading, empty, error, data-transition, and disabled-control states.

FrontendStatesUX
View details
RepoJun 2026

Agent-Ready API Spec Template

An OpenAPI and Postman starter template for APIs that AI agents can discover, call, and recover from safely.

OpenAPIPostmanAI agents
View details
DownloadJun 2026

UI PR Risk Review Checklist

A merge-readiness checklist for product intent, states, accessibility, visual durability, and UI implementation risk.

UI reviewQAFrontend
View details