CMP 1.8.0: Where to Draw the Shared Boundary, How to Sync Interop, and When to Roll Back
CMP's stable release lowers the entry cost, but without explicit boundary rules, interop contracts, stage-level performance diagnosis, and a fallback path that doesn't reference shared types, teams will sink time into shared code that can't ship or can't go back. These four checks turn 'CMP is usable' into a concrete go/no-go decision for a given feature or page.
CMP 1.8.0's benchmark numbers look good—startup comparable to native, scrolling on par with SwiftUI, ~9 MB size increase, and 96% of iOS developers reporting no major performance issues. But those are entry qualifications, not engineering decisions. The article lays out the three questions that actually determine migration success: where the shared boundary goes, whether the interop layer implements its synchronization contract, and whether performance problems can be attributed to the correct stage.
On the boundary, `commonMain` must compile to iOS and wasmJs with zero modifications; any `android.*` import, `LocalContext`, or Hilt reference breaks it. State belongs in `commonMain` via StateHolder + StateFlow, and the boundary layer defines contracts, not glue. Interop is treated as a synchronization contract between two rendering pipelines, not a wrapper: fixed bounds to avoid dual layout, `requestDisallowInterceptTouchEvent` on Android and `shouldRecognizeSimultaneouslyWith` on iOS for gestures, bidirectional focus bridging, and `remember`-stabilized lambdas to prevent native view recreation.
Performance attribution follows the TechnoAlpin case: zero recompositions and single-digit frame rate pointed to the drawing stage, where a Skia layer regression in 1.8.0 forced full subtree redraws; CMP-8491 fixed it in 1.9.0. Baselines must come from the same device and native implementation, with separate thresholds for Web. Finally, rollback is only real if the native fallback doesn't `import shared` and a verification checklist includes actually reverting both platforms.
The article treats the 'zero modifications' compilation check as the primary boundary test, which is stricter than most teams use—it forces platform-specific behavior into explicit `expect/actual` seams rather than letting leaks slip through.
The interaction between the 16ms frame budget and dual layout explains why even well-written interop code can feel sluggish: two independent layout engines each measuring in the same frame is a structural cost, not a tuning issue.
TechnoAlpin's case shows that the first instinct (recomposition) was wrong and the real cost was in drawing; the lesson generalizes: before optimizing, prove which stage the bottleneck belongs to, or you risk patching the wrong layer.
CMP's performance acceptance thresholds are explicitly tied to native baselines on the same device, which means the framework isn't judged in a vacuum—it's judged against the platform's own best case, a realistic but demanding standard.
The verification checklist's final step—actually reverting to native—is the most underused practice in cross-platform adoption; most teams only plan for fallback, but the article insists fallback must be exercised to count.
CMP is framed as a depth-selectable technology: the decision to share more isn't based on what Sony or TechnoAlpin did, but on four verifiable engineering conditions that any team can check.
The 15% threshold for stopping optimization is presented as a heuristic, not a universal rule, which is a refreshingly honest stance—it puts responsibility back on the team to define what acceptable degradation means.