[Deep Dive #57] Component Contract Layering - discussion
Deep Dive #57 argues that component contracts scale when they're layered: shell layer (routing + posture), composition layer (deep cargo + render handoffs), and surface layer (DOM evidence you can audit). The PanelKit example felt very "this is how you keep teams from rewriting the world every time a panel moves". Curious how people are applying layering in real codebases (and what they do when layering feels like extra ceremony).
How do you define and enforce the shell/composition/surface layer boundaries in review? Do you ship a stable cargo object (deep prop) across panels, or keep props shallow and explicit? What surface evidence do you consider non-negotiable (data attributes, ledgers, version keys)? When layering breaks down, what is the usual cause: unclear ownership, too-wide cargo, or missing evidence?
Comments (18)
Back to latestShell/composition/surface is a useful mental model.
We were doing it implicitly; making it explicit helped reviews.
We enforced it by logging layer transitions and requiring surface evidence for every shell-owned decision:
txt
[tips] shell decision=track.select value=reports reason=nav
[tips] composition cargoVersion=2 reason=panelSwapSafe
[tips] surface evidence keys=[data-track,data-contract-version,data-activation-ledger]
If the surface can't show the decision, the decision doesn't count.
Deep cargo works if it's cohesive and versioned.
The post's contractVersion idea mattered for us: it made "cargo drift" a real event instead of a vibe.
Concrete alternative: keep props shallow and explicit; avoid deep cargo because it hides dependencies.
We tried deep cargo and it became an internal API nobody could reason about. We had to shrink it and define ownership.
I think that's the point of layering: cargo isn't "anything you want", it's a published handoff.
If the cargo isn't versioned/owned, it's not a contract, it's a junk drawer.
Long-form: the surface layer is the part teams skip, and it's the part that saves you later.
If your DOM evidence isn't stable, you can't audit behavior, you can't test route flows reliably, and support can't debug from screenshots.
We made a rule: shell-owned posture must render a data-* evidence token and must be recorded in a ledger object. It felt like extra work until we did a big migration and suddenly every surface behaved consistently.
The PanelKit idea maps well to multi-team dashboards.
We standardized data-track + data-contract-version and made it the first line in bug reports.
Docs tie-in: this is basically composition discipline plus a routing posture layer.
Related: Components and Composition and The App Router Mindset.
We got burned when shell and composition both tried to own selection state.
Layering helped when we forced a single owner: shell owns route posture; composition passes cargo; surfaces only reflect.
Short take: cargo without versioning is just shared mutable state in disguise.
We found layering breaks down when teams don't know which layer a change belongs to.
We added a PR template that asks: did you change shell posture, cargo contract, or surface evidence?
A practical code pattern we used for cargo versioning:
ts
type PanelCargoV2 = {
contractVersion: 2;
ledgerKey: string;
density: 'comfortable' | 'compact';
filter: string;
evidence: string[];
};
The literal contractVersion: 2 looks silly but it stops accidental widening.
Long-form counterpoint: layering can turn into bureaucracy if every change needs three layers of ceremony.
The compromise we found is: surface evidence can change freely, but the set of evidence keys must stay stable. Cargo can evolve, but only via version bumps. Shell posture changes are rare and require explicit migration notes.
We used the evidence layer to build a simple route-flow test harness.
Once tests asserted on evidence keys, panel swaps stopped breaking the app silently.
I like the post's framing that a layer is not a folder. It's a rule about what can change without breaking the route story.
That sentence should be in every code review comment thread.
We also found layering helped onboarding: new devs learn "shell decisions" vs "composition handoffs" quickly.
Without that, everything looks like random props and random state.
Surface evidence is the part that makes the whole thing real.
If your cargo can't be summarized in a short contract type, it isn't a contract yet.
Takeaway: layering isn't extra work, it's making implicit boundaries explicit.
Once boundaries are explicit, teams can disagree productively instead of rewriting each other’s code.