// HACKER NEWS — CYBERSECURITY
The Magic Behind Cubacadabra
Before abracadabra was something you said over a top hat, it was something you wore around your neck. In the ancient Roman Liber Medicinalis, attributed to Quintus Serenus Sammonicus, the prescription was to write the word repeatedly, removing a letter each time, and wear the result as a charm against fever. An unusually literal approach to making a problem smaller.
If you've shipped iOS, Android, and web versions of something, you know the usual routine. Build the feature in Swift. Build it again in Kotlin. Build it again in JavaScript. Each version gets its own validation, requests, loading state, and error handling. They talk to the same backend, so we call them three clients of one product. We still wrote much of the product three times.
Then the feature changes. iOS learns how to recover from a failed save. Android gets the fix a week later. The browser handles the same error differently. Nobody set out to design three behaviors. They just accumulated while everyone was doing normal development.
I want cubacadabra's clients to become thin shells around Rust. Keep the minimum Swift, Kotlin, and JavaScript needed for native controls, device services, and browser integration. Move the portable application logic into shared Rust crates. That includes the boring account screens as well as the game engine. DRY, don't repeat yourself, should apply to the decisions the application makes.
A username field can still be a SwiftUI text field, a Compose text field, or an HTML input. Each forwards edits to Rust and displays the resulting state. Rust decides whether Save is enabled, which request to make, and what its response means. Changing the rule changes one implementation. The three screens can keep looking like they belong on their platforms.
There are good precedents. Litter has native iOS and Android interfaces over a Rust core that owns session state, streaming, and reconnect behavior, exposed through UniFFI bindings. Mozilla's shared Rust components grew out of maintaining separate sync implementations for Firefox's desktop and mobile apps. The reason will sound familiar: duplicated logic was hard to maintain, and differences between implementations caused bugs.
I worked through this in a long architecture discussion and the iOS development notes. A senior mobile developer could reasonably ask why a username field needs Rust, a C ABI, JNI, and WASM. Separate native implementations are easier to debug in their own IDEs. Bindings add build work and lifetime bugs. For a small app with a few forms, duplication could be cheaper.
For cubacadabra, I'm convinced the shared core is worth it. Rust already runs the engine and desktop Studio, and the browser already runs it through WASM. As the platform grows, joining a game will involve content compatibility, parental permissions, subscription access, blocked players, and recovery when a connection dies halfway through. I want to work out those interactions once. I don't want every new rule to become a coordination problem between three implementations.
The investment is starting to pay off. One iOS integration commit added 106 lines and removed 269, simplifying the username screens and their bridge. The first web integration grew because it needed the infrastructure. Future features should reuse that machinery. The test is whether their complicated behavior lives in one place and their host adapters stay small.
One implementation doesn't mean one enormous crate. cubacadabra-client owns the engine-facing multiplayer session. cubacadabra-app owns portable behavior outside gameplay. The engine owns simulation and rendering. A username save belongs in the app crate, where all three consumer clients can use it.