existence
← All posts
EngineeringSeptember 2026 · 5 min read

Don't move the floor: changing a shared layer without breaking the apps on top.

Every Existence app stands on a few shared foundations. Here is the versioning discipline that lets us change those layers without cracking the ground under a dozen products at once.

S
SiyabongaAuthor
XLinkedIn
changing the core, safely

Every Existence app stands on a small stack of shared foundations. The notifications layer, the localisation layer, the advertising layer: we built each one once so that every app after it could reach for the capability instead of rebuilding it. We have written here before about why that is worth doing. What we have not written about is the quiet cost of it, and the discipline we use to keep paying that cost down.

The cost is simple to state. When many things stand on one floor, you cannot move the floor casually. A change to a shared layer is not a change to one app. It is a change to every app that reached for it. Get it wrong and you do not break a feature, you break the ground under a dozen products at once.

Stability is a feature, not the absence of work

It is tempting to treat a shared library as finished the day it ships. It is not. A shared layer is a promise that gets made again every single time an app upgrades to a newer version of it. The whole value of pulling notifications into e_core_notifications evaporates the moment a team stops trusting that upgrading it is safe. If every version bump means an afternoon of archaeology to find what broke, developers pin the old version, the layers quietly fork in practice, and we are back to rebuilding notifications in five places.

So the real product of a shared layer is not the code. It is the confidence that you can adopt the next version on a Tuesday afternoon and get straight back to your actual work.

Semantic versioning, meant literally

We version every shared package with three numbers, and we mean all three. This is not decoration on a release note. It is a contract, and the contract tells a consuming app exactly what it is allowed to assume.

MAJORchanges only when a promise must break
MINORnew capability, old code untouched
PATCHa fix, and nothing else moves

Inside a single major line the rule is absolute: we add, we never remove, and we never change what an existing call already means. A new capability arrives beside the old one, not on top of it. Code written against version four keeps compiling and keeps behaving the whole way through the 4.x line. The day we genuinely must break something, the major number changes, and that change is loud, planned, and never a surprise.

Add beside, deprecate before you delete

Most changes are additions, and additions are the easy case: a new function next to the old one costs nobody anything. The hard case is when something old truly has to go. We do not delete it out from under people. We deprecate it, which for us is a sequence rather than a single event:

  • Mark the old path as deprecated, in the code and in the changelog, with the new path named right beside it.
  • Keep it fully working. A deprecation is a warning, not a removal.
  • Give every app a window to move, measured in releases, not in hours.
  • Remove it only at the next major version, which every team already knows is coming.

The result is that nobody arrives on a Monday to a build that broke overnight because a shared layer decided to tidy itself up.

The measure of a shared layer is not how fast it can change. It is how much can safely be built on top of it without anyone having to watch it.

The demo app is the first to fall

Every Existence package ships with a working demo: a small, real application that exercises the layer for its own sake. We built those demos to prove that a package actually runs. They earn their keep a second time as canaries. When we change a core layer, its demo app is the first thing that has to keep working, and the first place a careless change shows up. A regression that would otherwise have reached a production app tends to break the demo first, in a place where breaking is free and nobody is watching but us.

Roll it forward, one app at a time

When a new major version of a shared layer is ready, we do not push it everywhere at once. We move one app onto it, watch it in the real world for a while, and only then move the next. A shared foundation tempts you toward the big-bang upgrade, because updating everything together feels efficient. It is not efficient. It is just a larger blast radius. Deliberate and app-by-app reads as slower on a roadmap, and is far kinder to the people who have to trust the result.

The boring discipline is the whole point

None of this is clever. There is no trick here, no framework to install. It is a set of small refusals: refuse to change a signature within a major line, refuse to delete without deprecating first, refuse to upgrade everything in a single move. Each one is dull on its own. Together they are the reason a developer on one app can lean an entire product on a layer someone else wrote months ago, and never once think about it.

That is what we are really building when we build a shared core. Not just notifications, or localisation, or ads. A floor that stays exactly where you left it, so that the people standing on it can spend their attention looking up.

S
SiyabongaWriting for Existence

Notes, as they happen.

One email when we publish. No digest, no drip, just the post.