Home avatar

ISLAND

SugarLite KMP in Production: A 3-Second-After-Launch Crash and the Cost of Uncaught viewModelScope Exceptions

When we closed Part 3, we said the post-migration routine is “new features default to KMP-first.” That’s true — but it hides a premise: shared-layer code does not run in equal environments on the two platforms. The same Kotlin code that survives as a catchable exception on Android can kill the entire process on iOS.

This post documents a real incident: the iOS build of SugarLite crashed with SIGABRT exactly 3 seconds after every cold start, with a stack pointing at an unfamiliar symbol — propagateExceptionFinalResort. The root cause wasn’t a single line of Swift. It was a handful of innocent-looking Flow subscriptions in our shared ViewModels — their exceptions were never caught, and on Kotlin/Native that means the process dies.

SugarLite's Incremental KMP Migration (Part 3): Direct Observation with KMP-ObservableViewModel — iOS ViewModels Retire

At the end of Part 2, our answer was “KMP ViewModel + SKIE + a 50-line StateHolder per ViewModel.” We claimed the cost was low — usually under 50 lines of bridging per ViewModel.

We later took that statement back.

The per-VM cost was indeed low, but it was a quantity tax — 23 ViewModels meant 23 StateHolders, each a copy-paste of @Published property mapping, a for await subscription loop, and a manual deinit cancellation. Worse, the KMP ViewModel lifecycle on iOS was left dangling: androidx.lifecycle.ViewModel has no ViewModelStore on iOS, so viewModelScope was never cancelled by the system — only our hand-written deinit cancel() calls kept things tidy. Miss one, and you leak a coroutine.

opencodex: Run Codex and Claude Code on Any LLM

opencodex is a local proxy that translates Codex’s Responses API into whatever protocol your LLM provider speaks. You can use Claude, Gemini, DeepSeek, Grok, GLM, Qwen, or even a local Ollama model inside Codex CLI, Codex App, and SDK — and in Claude Code too — without waiting for anyone to add official support.

Streaming, tool calls, reasoning tokens, image generation — everything works, in both directions.

npm install -g @bitkyc08/opencodex
ocx init
ocx start

Three commands, and Codex is rerouted. It forwards your ChatGPT login by default; you can add more models anytime.

SugarLite's Incremental KMP Migration (Part 2): ViewModels, Local Storage & Platform Abstractions

Part 1 documented how SugarLite sunk its data layer from the Supabase Swift SDK down into the KMP shared/ module. But we left one thing deliberately untouched — ViewModels stayed on iOS using @Observable. We said we’d “keep them put” for now.

Well, we broke that promise.

As Android development accelerated, 21 of our 23 ViewModels needed to work on both platforms. Maintaining one set of Swift ViewModels for iOS and another set of Kotlin ViewModels for Android was clearly unsustainable — why write the same business logic (fetch today’s blood sugar, build a timeline, calculate glycemic response) twice?

SugarLite's Incremental KMP Migration (Part 1): Sinking the Data Layer & Adapting CI/CD

SugarLite started as a pure iOS app built with SwiftUI + MVVM + Supabase Swift SDK. When the Android version became a priority, copying the business logic over to Kotlin would have doubled the maintenance burden — network layer, data models, and business rules all needed dual-platform upkeep. That’s why we chose Kotlin Multiplatform: the shared/ module carries all cross-platform business logic, while the iOS side only keeps SwiftUI and system framework calls (HealthKit, SwiftData, WidgetKit).

Configuring Claude Desktop in CC Switch

I’ve previously covered the basics of CC Switch. Today, let’s take a closer look at the Claude Desktop panel — added in recent versions specifically for connecting the official Claude Desktop client to third-party providers.

In CC Switch, “Claude” and “Claude Desktop” are two separate panels. They map to Claude Code (the CLI) and Claude Desktop (the desktop client), respectively. Claude Desktop uses its own 3P profile config rather than ~/.claude/settings.json. The two panels are distinguished by a small badge on the icon.