Every Shared Module Has Two Halves
A shared module deliberately does not know its app. Something has to supply the missing knowledge, and that something is the shim: a small file, owned and authored by the app, that binds the shared machinery to this app's constants and wraps it in this app's vocabulary.
Shims are outside the deploy manifest. They are never generated, never checked for drift, and the shared repository has no idea they exist. That asymmetry is the point — the copies are byte-identical everywhere and the bindings are all different, and each half is enforced in the way that suits it.
What Actually Goes in One
Three things, in practice, and no more:
- Constants the shared code takes as parameters. A key slot name, a surface identifier, a database path. The shared module wrote a parameter precisely so it would not have to know these.
- Re-exports that preserve the app's existing call sites. When shared machinery replaces hand-written code, the shim can expose the same function names the app already calls, so adoption is a one-file change rather than a hundred-call-site change.
- Domain wrappers. The app's own functions, expressed in its own language, that happen to call shared machinery underneath.
What must never go in one is a modification of shared behavior. If a shim finds itself reimplementing part of what it wraps, that is the signal that either the shared module needs a parameter it does not have, or the behavior was never shared to begin with.
The Shim Is Where Divergence Is Allowed to Live
There is a second, quieter function. When one app genuinely needs something different, the first question is always whether the difference can live in the shim. Often it can — a different key slot, a different surface name, an extra wrapper — and then the shared module never learns that any app is special.
Only when the difference cannot be expressed in a shim does the shared module need to grow a parameter, and only when it cannot be expressed as a parameter does anything harder need to happen. That ordering is worth holding explicitly, because the instinct under time pressure runs the other way: reach into the shared file first, because that is where the code is.