Course outline

React Native / Inclusive native systems and release evidence / Lesson 17

Offline intent and command reconciliation

Separate locally retained intent from confirmed business state and preserve stable identity across retries and account changes.

About 4 min readContent reviewed
On this page

Lesson goals

  • A local queue preserves intent rather than acceptance
  • Keep one operation identity across attempts
  • A lost response creates uncertainty, not rejection

A local queue preserves intent rather than acceptance

An offline action can record what the user wants while a service is unavailable, but cannot promise the service will accept it later. Capacity, permission, account ownership, and record revision can change before delivery. Expose queued, sending, confirmed, rejected, and uncertain states distinctly. Device connectivity does not establish endpoint reachability or successful authorization. Decide which operations are allowed offline and what the user may cancel before or after sending. AppState activation can trigger status reconciliation under a clear owner, but should not blindly repeat the most recent consequential command.

Keep one operation identity across attempts

The first program creates an immutable synthetic command envelope with a caller-supplied operation identifier. That identifier belongs to the command, not to each transport attempt. Retrying with a fresh identifier can defeat the authoritative service’s duplicate-detection or idempotency policy. Account identity is stored with the intent so restart or login cannot silently send it under another owner. The example is an in-memory value only; persistence, uniqueness generation, encryption, and server recognition are separate contracts. No actual reservation, background task, or queue store is created by this code.

javascript Standalone JavaScript model with synthetic local inputs.
function command(operationId, accountId, workshopId, seats) {
  if (![operationId, accountId, workshopId].every(value => typeof value === 'string' && value !== '')
      || !Number.isSafeInteger(seats) || seats < 1) throw new Error('INVALID_COMMAND');
  return Object.freeze({operationId, accountId, workshopId, seats});
}
const queued = command('op-7', 'lab-user', 'w-7', 2);
const retryPayload = {...queued};
console.log(retryPayload.operationId);
console.log(retryPayload.accountId);
console.log(Object.isFrozen(queued));
Expected output
op-7
lab-user
true

Retry copies preserve the original command and account identity. Object freezing is shallow; this envelope contains primitive fields only.

A lost response creates uncertainty, not rejection

The second program models a lost response after sending as uncertain. The server might already have committed, so ordinary rejection and lost confirmation require different recovery. Reconcile by the stable operation identity through the service’s documented contract before repeating an unsafe command. A local reducer cannot guarantee exactly-once delivery or invent a server-side transaction rollback. The shown reducer preserves state for unsupported transitions; a production API may instead report an invalid event explicitly. Choose that policy deliberately and ensure the visible status reflects the authoritative facts available rather than hiding ambiguity behind a success badge.

javascript Standalone JavaScript model with synthetic local inputs.
function transition(state, event) {
  if (state === 'queued' && event === 'send') return 'sending';
  if (state === 'sending' && event === 'accepted') return 'confirmed';
  if (state === 'sending' && event === 'refused') return 'rejected';
  if (state === 'sending' && event === 'response-lost') return 'uncertain';
  return state;
}
console.log(transition('sending', 'response-lost'));
console.log(transition('sending', 'accepted'));
console.log(transition('uncertain', 'accepted'));
Expected output
uncertain
confirmed
uncertain

An uncertain operation is not confirmed by an unsupported transition. A real reconciliation result needs an explicitly designed event and service policy.

Ordering and cancellation depend on domain meaning

Two queued commands can be independent or causally related. Editing a title then deleting the record is not equivalent to deletion followed by a late edit. Define per-record ordering, replacement, conflict resolution, and account scope before adding parallel delivery. Last-write-wins is not universally correct for every domain. Cancellation before send can remove local intent, while cancellation after send may require a compensating command or be impossible. Keep unresolved records long enough for safe reconciliation under a reviewed retention policy. Never resend a previous account’s private command merely because a new token is available.

Test restart gaps and ownership using fakes

Simulate restart after enqueue, after send before confirmation, and after an explicit rejection. Verify retries preserve identity, a session change prevents old publication, and dependencies remain ordered. A fake store and transport can establish application decisions without devices or remote services. Serialization alone does not prove durable queue recovery, and a fake response cannot prove server idempotency. Target background scheduling, native storage, account authorization, and actual delivery require separate authorized integration evidence. The course runs only pure models and does not operate a live queue, database, scheduler, or reservation system.

Common errors and fixes

Queued intent is shown as confirmed

Why it happens
Local recording was confused with authoritative acceptance.
How to fix it
Expose pending status until confirmation or rejection is known.
Prevent it next time
Test a delayed server rejection after local enqueue.

Every retry receives a new operation ID

Why it happens
Identity was attached to attempts rather than commands.
How to fix it
Persist one command identity and reuse it under the service contract.
Prevent it next time
Simulate a lost response followed by retry.

Logout sends old intent as the next user

Why it happens
Queued work lacked account ownership.
How to fix it
Retain or discard old intent according to explicit account policy.
Prevent it next time
Change sessions before delivery and reconciliation.

Check your understanding

1. Does a stable operationId guarantee idempotency by itself?

Show worked solution

No. The service must define how it recognizes, scopes, retains, and answers repeated identifiers. The client preserves identity so that contract can work. Without authoritative support, reusing the same string does not prevent duplicate side effects.

2. Why should an uncertain record not be deleted merely to clear a badge?

Show worked solution

It may be the evidence needed to reconcile a command already accepted remotely. Deleting it can hide an unresolved outcome and encourage an unsafe duplicate. Preserve safe identity under the retention policy and present uncertainty honestly rather than inventing rejection or success.

Key takeaways

  • A local queue preserves intent rather than acceptance
  • Test restart gaps and ownership using fakes

Official references