Loading, Empty, Error, Offline and Conflict: The Invisible Product Architecture
A reliable product knows how to behave when data is missing, writes fail, the device is offline or two valid states conflict.
A demo is easy to design because the world is cooperative.
Data loads. Cards appear. The user clicks. The mutation succeeds.
Production is less polite. Networks slow down, sessions expire, lists return empty, writes fail, two devices change the same record, tabs sleep and local state becomes older than cloud state.
This is where product architecture becomes visible.
The happy path is one state, not the product
I do not consider a surface complete because its normal state looks correct. At minimum, I want deliberate behaviour for:
Loading — does the user understand what is pending? Empty — is there truly no data, or did a filter, permission or failed query create the appearance of emptiness? Error — what failed and what can the user safely do next? Offline — can the user read, write or queue work? Conflict — what happens when two valid versions disagree?
These are not microcopy states added after the implementation. They depend on domain, persistence and synchronization decisions.
Empty is not one state
“Nothing here yet” can hide several very different realities:
- the user has never created an item,
- the current folder has none,
- search returned no results,
- synchronization has not completed,
- permissions hide the data,
- the query failed and the UI collapsed into an empty array.
If all of those produce the same empty state, the interface is rendering but telling the wrong story.
Offline is an architecture decision
“Support offline” sounds like a feature flag. It immediately creates harder questions:
- What can be read locally?
- Can the user write while offline?
- Where are pending mutations stored?
- Do they survive an app restart?
- In which order are they flushed?
- What if the server revision changed?
- How is a conflict represented?
In a writing product such as Kurmaca, putting a draft into localStorage is not sufficient. Recovery has to know which revision it belongs to and how it returns to the canonical history.
Error is a recovery path, not a red toast
“Something went wrong” tells the user almost nothing.
A useful error state answers three questions:
1. What failed? 2. Was my work preserved? 3. What is the next safe action?
If a save fails, the product may need to know whether a local draft still exists, whether retry is safe, whether a duplicate write is possible and whether navigation should be blocked.
Conflict is an honest state
For some data, last-write-wins is a valid decision. For critical content, silent overwrite may be unacceptable.
A conflict state simply says: two legitimate realities exist and the system cannot safely choose on the user’s behalf.
That is not necessarily failure. It is the system refusing to hide ambiguity.
Put states into the acceptance contract
A small matrix makes the work concrete:
- State
- Loading
- What the user sees
- Contextual loading UI
- Can they write?
- No
- Recovery
- Wait
- State
- Empty
- What the user sees
- Explanation + primary action
- Can they write?
- Yes
- Recovery
- Create
- State
- Error
- What the user sees
- Scope of failure
- Can they write?
- Depends
- Recovery
- Retry / return
- State
- Offline
- What the user sees
- Offline state
- Can they write?
- Domain-dependent
- Recovery
- Queue / local draft
- State
- Conflict
- What the user sees
- Competing revisions
- Can they write?
- Controlled
- Recovery
- Resolve
| State | What the user sees | Can they write? | Recovery |
|---|---|---|---|
| Loading | Contextual loading UI | No | Wait |
| Empty | Explanation + primary action | Yes | Create |
| Error | Scope of failure | Depends | Retry / return |
| Offline | Offline state | Domain-dependent | Queue / local draft |
| Conflict | Competing revisions | Controlled | Resolve |
The exact table changes by product. The important point is that frontend, backend and persistence share the same behavioural contract.
Product trust is not built only when everything succeeds. It is built when the system behaves coherently when reality is messy.
Architecture in the Wild
Field architecture through live state, information, interaction and product-boundary decisions rather than abstract diagrams.