[Guide] Build an Inventory System (Global Mutable Objects) - implementation notes
The Inventory System guide leans into global mutable objects for speed and predictability, then tries to keep the rest of the app sane with mirrors, write helpers, and stored-derived stock alerts. It's a very specific posture: accept mutability, then make it observable and contract-driven so UI coherence doesn't collapse.
How did you keep global mutable objects from becoming a hidden global store with spooky action at a distance? What did your mirror layer look like (snapshots, versioning, lanes) so React renders stayed coherent? How did you structure write helpers so every mutation is logged and derivations run reliably? What derived stock alerts did you store, and what evidence did you render so ops can debug from screenshots?
Comments (24)
Back to latestGlobal mutable objects are survivable if you treat them like infrastructure and gate access.
If every component can mutate, you're done. Write helpers are not optional.
We made every mutation go through a write helper that logs intent + outcome and triggers derive exactly once:
txt
[tips] intent=stock:adjust sku=SKU_19 delta=-3 lane=pending reason=user:submit
[tips] intent=stock:adjust sku=SKU_19 delta=-3 lane=ok reason=apply:mutation newQty=12
[tips] derive=stockAlerts reason=stock:adjust affected=SKU_19 alerts=2
The last line matters because derived alerts are part of the product truth (ops uses them).
Counterpoint: global mutable objects can trick teams into skipping React mental models.
If the mirror layer isn't explicit and replayable, you get renders that don't correspond to any coherent state.
Yes. The mirror layer is the real product contract.
The mutable object is just a storage optimization; the mirror is what the UI sees and what tests can assert.
We versioned the mirror snapshot (mirrorRev) and rendered it as evidence in debug mode.
It prevented "list and detail disagree" bugs because you can see which snapshot each panel is on.
Long-form: the biggest risk with global mutable objects is not correctness, it's *auditability*.
Inventory is an audit domain. If you can't explain why quantity changed, you can't operate the system.
Write helpers + contract logs are the only way this posture is viable in production.
Mirrors worked best when they were treated as snapshots, not as live views.
We copy the minimal render-ready shape into the mirror, then React renders from that stable shape.
We used a gate for mirror refresh so we don't churn on rapid adjustments, but we logged gate decisions:
txt
[tips] gate=mirrorRefresh allowed=false reason=burstAdjust remainingMs=88
[tips] gate=mirrorRefresh allowed=true reason=debounce:fireCounterpoint: the mirror layer can become a second database if you store too much.
We stored only render-ready fields and derived alerts, not the whole world.
We also rendered evidence for the last write helper intent and last derive reason.
When ops says "why did this alert appear", the UI can answer without opening logs.
Docs tie-in: this is a state management posture problem: where does truth live and how do you observe it?
Related: State Management (Best Practices) and Patterns Library.
We structured alerts as stored-derived outputs with a version and a summary string.
Summary string was key: "low stock: SKU_19 (12)" reads like a sentence and prevents misinterpretation.
Long-form counterpoint: mutable globals can make concurrency bugs easy to create (two writes interleave).
We solved it by serializing writes through the helper and logging the queue.
If the system is eventually consistent, the queue should be visible as a lane (queued/pending/ok).
We wrote a small invariant: mirrors must be derived only from mutable storage, never mutated directly.
Violating that invariant creates drift that is almost impossible to debug.
Counterpoint: inventory UIs often need optimistic updates; strict snapshot mirrors can feel slow.
We allowed optimistic values in the mirror but tagged them as optimistic and rendered that as evidence.
Long-form: the guide works when you accept that global mutability is a *tradeoff* you must pay for.
The payment is: explicit boundaries, centralized writes, stable mirrors, and aggressive evidence/logging.
We found that most inventory bugs were actually UX bugs: the UI didn't explain which snapshot you're looking at.
Once we rendered snapshot version + last derive reason, support tickets got shorter.
Counterpoint: this posture encourages people to do more work in the "global object" because it's fast.
We enforced a rule: global object holds raw state, never UI-level derived state. Derived state belongs in the derive step and must be logged.
We also logged derived alert changes as diffs (added/removed alert ids).
It made it easy to see why a panel changed without reading raw stock numbers.
Long-form: inventory is one of the few domains where "global mutable objects" can actually make sense.
The domain is inherently global and shared. The mistake is pretending the UI can be global without a mirror contract. Mirror contract is what makes React work here.
If you're implementing this, start by writing the write helper API and the log format first.
Once writes are centralized and observable, the rest of the system becomes tractable.
We also stored derived "stock health" (ok/warn/critical) so the UI doesn't recalculate thresholds in multiple places.
Threshold drift is a real bug in inventory apps.
Counterpoint: you can get similar speed with immutable data if you design your derive layer well.
We tried both. Mutable globals were faster to prototype, but the mirror/derive discipline was what determined whether it was maintainable.
We treated write helpers as the only place that can mutate and also the only place that can emit lane transitions.
That consistency made it possible to build a dashboard of inventory behavior later.
The strongest part of this guide is the honesty: if you choose mutability, you must choose observability too.
When that pairing is respected, the app feels surprisingly stable.