Simplexable
Postcards from My Edge

Journey: Three Generations of Caching Architecture

Clark Wilkins

Summary

Don't automatically accept everything the AI suggests.

Using two AIs with separate roles — design and implementation — proved extremely effective. Having them critique each other helped surface errors that might otherwise have gone unnoticed.

Caching architecture determines how applications store and refresh shared data so systems can remain fast while maintaining consistency.

Some of my colleagues know the backstory of the Uniti.js ERP project, but this post focuses on one specific engineering problem — and the path it took to solve it.

About two and a half years ago I started working on an idea to speed up component loading by caching things that don't change very often. Lists like inventory stocking locations or the Chart of Accounts are what I call semi-static objects, or SSOs.

The concept seemed straightforward: cache these objects once per session and refresh them only when someone actually changes them.

Simple idea.

Not such a simple implementation.

Phase 1: Throw S__t Against the Wall

When I started the first version of this idea, AI-assisted development tools like Cursor were still in their infancy. That meant rewriting roughly 150 API routes and nearly 400 React components by hand.

The entire effort took about two months including design and documentation.

The system sort of worked. I was able to cache SSOs effectively within a single browser tab. I also built a clunky mechanism to notify all open tabs — across all users — when a cached dataset changed. Within about ten seconds every tab would know it needed to refresh.

It wasn't elegant, but it mostly worked.

And time moved on.

Phase 2: Seemed Like a Good Idea at the Time

Later, I started using Cursor.ai heavily. Performance challenges were beginning to surface, so I had long discussions with Claude Sonnet and ChatGPT about possible improvements.

The decision — largely suggested by the AIs — was to move to a client-side caching architecture.

Everything would be cached directly in the user's browser. The React application running there would synchronize data across all open tabs using browser tab coordination.

We called this system the Distributed Objects Cache.

At first it seemed to work quite well.

Until it didn't.

The Breaking Point: High Latency and "Render Storms"

Eventually I started noticing strange behavior. If I stepped away from the browser for a few minutes — maybe to check email — I would return to find the interface reset to its default state.

Forms that had been partially filled in were gone. Modal dialogs had disappeared. The application had essentially reloaded itself.

This became especially noticeable during a recent trip to French Polynesia, when I occasionally checked in on things using networks with terrible latency and bandwidth.

Under those conditions many tools became almost unusable.

That experience made something obvious:

Phase 2 had to go.

Phase 3: This Time It Will Be Different

The new architecture reversed the previous idea completely.

Instead of pushing caching to the client, we moved it back to the server, replacing the front-end cache logic with a set of Zustand stores.

In fact, about 30 separate stores.

Each store handles a narrow slice of semi-static data. This allows updates to propagate only where they are needed. For example, a change to stocking locations should not trigger rendering changes in accounting tools.

This project also forced me to do something I had historically avoided:

Write a detailed plan before coding.

I used Opus 4.6 in Cursor to generate an initial architecture plan. That plan was sent to ChatGPT for review. ChatGPT provided design feedback, which went back to Opus.

Rinse.

Repeat.

Sometimes thirty times.

Once half the SSOs were converted, we paused to unify the architecture. A Cursor Rules file was created to describe exactly how each store should be implemented. Repeated logic was extracted into helper functions following the DRY principle.

Then the remaining stores were generated.

The final conversion process took about an hour.

Learning, designing, writing, testing, and debugging the same system manually would likely have taken several months.

Coding Is Now 10% of the Process

The surprising part wasn't the coding.

It was the documentation.

Roughly 30 hours were spent updating documentation for the third-generation SSO architecture. New components, helper functions, and design rules all needed to be recorded.

After about ten conversions I noticed a pattern: much of the documentation repeated the same structural elements.

Since the documentation was written in HTML, I created two reusable snippet files. Cursor could then update those templates automatically for each SSO while preserving the formatting.

That simple trick saved enormous time.

Takeaways

  • Plans are worth the effort. I had heard this many times but always felt I didn't have time. It turns out the opposite is true.
  • Use multiple AIs with different roles. In this project ChatGPT acted as the design reviewer while Cursor/Opus generated code.
  • Experiment when you don't yet understand the problem. Early experiments revealed patterns that eventually led to the final architecture.
  • The AI is not always right. Near the end of Phase 3, ChatGPT identified several possible issues in the logs. Cursor later confirmed two of them and rejected the third. Cross-checking matters.

Closing Thought

This particular problem took more than two years of experimentation to work through.

Hopefully there won't be a Phase 4.

In a later post I describe how this experience evolved into a structured two-AI development workflow using ChatGPT and Claude Opus. Read: "ChatGPT and Opus Sitting in a Tree: A Two-AI Development Workflow".

Clark Wilkins

Chief Improviser

Simplexable LLC