Why the Cleanest Systems Keep Their Past in One Place
Hatched by Honyee Chua
May 14, 2026
9 min read
11 views
84%
The hidden rule behind responsive systems
What do a tic-tac-toe board in React and an app that auto-updates its manifest have in common? At first glance, almost nothing. One is a tiny game, the other is maintenance tooling. But both are obsessed with the same problem: how to change a system without letting it become inconsistent.
That is a deeper question than it first appears. The real challenge in software is not making things happen. It is making sure the right things happen in the right place, at the right time, without creating accidental side effects. A UI that lets two squares disagree about the board is broken. A package manifest that drifts out of date is broken. In both cases, the failure is not merely technical. It is architectural.
The most durable systems are not the ones that allow the most direct manipulation. They are the ones that carefully decide where truth lives, who is allowed to change it, and how changes are propagated.
Why local convenience often creates global chaos
A beginner’s instinct in user interface code is simple: let each part own itself. A square can hold its own value. A button can handle its own click. A manifest can be edited directly when a version changes. This feels clean because it reduces coordination in the short term.
But local ownership has a hidden cost: it fragments reality. If each square keeps its own state, then the board becomes a collection of private opinions rather than a shared game. If each file is manually edited whenever a package changes, then maintenance becomes a slow drift of inconsistent versions. The system may still run, but it loses coherence.
This is why the idea of lifting state up matters so much. It is not just a React trick. It is a general design principle: when multiple parts need to agree, move the source of truth to the smallest common parent that can coordinate them. The board owns the game state because the board is the level at which the game is actually understood.
The same pattern appears in automation. A script that checks versions and updates manifests is doing more than saving keystrokes. It is relocating responsibility from human memory to an executable process. The maintenance burden is pulled upward into a more reliable layer, where the whole set of files can be compared and updated consistently.
A system becomes easier to reason about when the thing that changes is not scattered everywhere, but concentrated in one place that everyone else can trust.
That is the first big insight: complexity often comes from distributing responsibility too widely.
State is not just data, it is authority
A lot of people think state is simply information. In practice, state is also permission. Whoever owns the state decides what counts as real. In a component hierarchy, a child can display data, but it should not quietly rewrite the parent’s truth behind the parent’s back. In a package workflow, a single file should not become an island of manual edits when a script can update the canonical manifest set.
This is why React treats state as private to the component that defines it. Privacy here is not secrecy, it is discipline. It prevents a child component from becoming a rogue authority. The child can request a change by calling a function passed down through props, but it does not directly seize control of shared reality.
That distinction matters because software breaks when observation and mutation are confused. A component can observe state and render from it. A script can inspect versions and report outdated packages. But once a component or tool mutates shared truth, that mutation should happen through a deliberate pathway. Otherwise the system becomes a patchwork of side effects that are hard to trace and impossible to undo confidently.
This is why immutable updates are so valuable. When the board creates a copy of its squares array and changes the copy, it preserves the old version long enough to compare, rewind, or validate. When an updater checks manifests before writing changes, it creates a safe boundary between reading and modifying. Immutability is not dogma. It is memory with guardrails.
Consider an office whiteboard used by several teams. If everyone erases and rewrites directly, the board becomes a source of accidental conflict. If instead people propose edits on paper first, then one person updates the board, the shared record stays coherent. The copy is not wasted effort. It is the checkpoint that makes coordination possible.
The real power of callbacks and automation is not convenience, but decoupling
One of the most interesting patterns in the tic-tac-toe tutorial is the way a square does not change the board directly. Instead, it receives an onSquareClick function. That function is passed down, then called when the user clicks. This looks like a small implementation detail, but it is actually the essence of scalable design: the child signals intent, the parent decides what to do.
That same pattern is visible in manifest autoupdate workflows. A script like checkver.ps1 can inspect apps, detect outdated versions, and optionally update manifests. The script is not just a robot that pushes buttons faster than a human. It is a mediator between evidence and action. It separates the question “Is this outdated?” from the decision “Now update it.”
This separation is powerful because it creates a system that can be composed. A child component can signal a click without knowing whether the parent will change a move, record history, or ignore the action. A version checker can report status without knowing whether the operator wants to update one app or all of them. The producer of a signal does not need to own the response.
That is the architecture of mature systems. They do not fuse every concern together into one tightly coupled mechanism. They create interfaces between intention and effect.
A useful mental model is this: events should travel upward, truth should travel downward.
- A button click travels upward as an event.
- Shared state travels downward as props.
- A manifest scanner reports upward what it found.
- An update command writes downward into the files.
This bidirectional flow keeps the system legible. It also makes it easier to test, because you can inspect one direction without simulating the whole universe.
Why undo is the ultimate test of good design
There is a quiet clue in the tic-tac-toe example: the game wants to support undo and redo. That requirement changes everything. If state is mutated in place, the past gets destroyed. If every move overwrites the current reality without preserving history, then stepping backward becomes difficult or impossible.
This is why immutability is so often paired with history. A new copy of the board after each move gives you a chain of snapshots. Each snapshot is a stable fact. You can revisit it, compare it, or branch from it. In other words, good design treats time as a first class concern.
Now look at manifest autoupdate through that lens. An automated updater is not only about convenience. It is also about producing a cleaner change history. If version changes happen through a repeatable command, then updates become auditable events instead of ad hoc interventions. The difference shows up later, when you need to know what changed, when, and why.
This is the deeper shared principle: the best systems are designed for reversibility.
Reversibility is not just for games or version control. It is a hallmark of robustness in any environment where mistakes are expensive. A system that can be rolled back is more honest about uncertainty. It admits that some actions should be safe to inspect before committing.
A practical example: imagine editing a shared spreadsheet. If people overwrite cells directly, errors spread instantly. If edits are staged, reviewed, and applied as a new version, mistakes remain containable. The second approach feels slightly slower at first, but it creates trust at scale. That trust is what allows the system to grow.
A framework for building systems that stay coherent
Taken together, these patterns suggest a simple but powerful framework for designing software and workflows.
1. Decide where truth belongs
Not every piece of data should live where it is first used. Ask where the data is actually coordinated. In a game, that is often the board or game engine. In a maintenance workflow, that may be the manifest repository or update pipeline.
If two or more children need to agree, the truth probably belongs in their parent or in a centralized process.
2. Separate display from control
Components and tools should be able to show information without being granted unlimited authority to change it. Display is cheap. Control is expensive. Conflating them produces fragile systems that are hard to debug.
3. Prefer new copies when the cost of mistakes is high
Direct mutation is tempting because it is immediate. But copying before changing creates a buffer. That buffer is what enables undo, comparison, review, and safe propagation.
4. Use functions and scripts as boundaries, not shortcuts
A callback is not merely a way to avoid repeating code. It is a contract: “Here is the action to take when the event happens.” Likewise, an autoupdate script is not just a convenience. It is a repeatable policy encoded in execution.
5. Optimize for consistency before speed
Fast local edits can create slow global cleanup. A slightly more structured system often wins because it stays coherent under change.
These principles apply far beyond React and package management. They describe why operating systems have privileged layers, why databases use transactions, and why editing tools track version history. Everywhere that coordinated truth matters, systems evolve toward the same shape: centralize authority, distribute observation, and preserve history.
Key Takeaways
- Shared truth should live in the smallest common parent that can coordinate all dependents.
- State is authority, not just data, so control it carefully and avoid direct mutation from unrelated parts of the system.
- Callbacks and scripts work best as boundaries between intent and action, not as tangled shortcuts.
- Immutable updates make undo, redo, and auditability possible, because they preserve past versions instead of destroying them.
- Consistency is a design choice, and it usually requires giving up some local convenience in exchange for global coherence.
The deeper lesson: good systems remember without getting confused
The surprising connection between a tiny UI game and an update script is not that both involve code. It is that both are about remembering correctly. A well designed component tree remembers state in one place and distributes it predictably. A well designed maintenance workflow remembers what versions exist and updates them in a controlled way.
This is what makes such systems feel calm when they are working well. They do not pretend that change is free. They acknowledge that every update has consequences, and then they create a structure that can absorb those consequences without collapsing into inconsistency.
So the next time you design a feature, a workflow, or even a personal system, ask a better question than “How do I make this change?” Ask instead: Where should the truth live so that change remains intelligible?
That question changes everything. It is the difference between a system that merely functions and one that can survive its own growth.
Sources
Hatch New Ideas with Glasp AI 🐣
Glasp AI allows you to hatch new ideas based on your curated content. Let's curate and create with Glasp AI :)
Start Hatching 🐣