Infografía · Blupoli

La web como núcleo, Android como superficie

WebProducto
CapacitorPuente
AndroidDistribución
Una lectura visual del sistema de restricciones que define este capítulo.

When the first version of this article was published, the project was still called Blupoli Puzzles. Today the product is Blupoli Puzzles, but the decision it describes remains a useful snapshot of how we try to reason about architecture: choose the least irreversible option while the product is still teaching us what it needs. By September 2026 we already had a substantial web platform—engines, catalogue, editorial content, styling, persistence and a growing set of product rules. At the same time, one question was becoming unavoidable: how do we bring that experience to Android without turning every future improvement into two separate projects?

The answer was not obvious because native Android is familiar territory for us. Kotlin, Jetpack Compose and native app architecture were not unknown alternatives. That familiarity actually made the comparison stricter. Starting a new Android application from scratch would have been technically feasible. It also would have meant reimplementing a very large surface before learning whether reusing the web would cause any serious product limitation. Choosing Capacitor as the first move was therefore a sequencing decision, not a declaration that web technology is always superior to native development.

The right architecture depends on the product’s current stage

Two projects with the same destination can need different technical choices because they start from different assets. If Blupoli Puzzles had begun life as a mobile app, native Android might have been the natural foundation. But our existing asset was the web. The engines were built to run in the browser. Catalogue pages were generated from repository data. Navigation, editorial surfaces and localization already participated in a build pipeline. The shared interface system was still evolving.

A rewrite at that moment would have frozen too many boundaries too early. Moving an experience requires decisions about engine contracts, state representation, shared components, metadata and domain ownership. If those things are still changing every week, a second client becomes another set of constraints. Every web refactor immediately raises a second question: repeat the change on Android, or allow the two models to diverge for a while?

That is why the question changed from “What is the most elegant mobile architecture?” to “What gets us onto Android without splitting the product yet?” In that context, reuse-first and extract-later was the more conservative experiment.

A second implementation duplicates more than code

Describing a rewrite as a line-count problem misses most of the cost. Two clients create two places where a product decision can diverge. A puzzle bug can be fixed on the web but remain on Android. A difficulty model can evolve in one engine and freeze in another. Onboarding, accessibility, theming, persistence and layout can acquire separate timelines.

Even with a shared domain layer, the product surface remains wide. There are empty states, errors, navigation, help, statistics, preferences, content updates and responsive behavior. When a product is still discovering what “good” looks like, maintaining two mature clients can turn every lesson into synchronization work.

None of this means duplication is never justified. It means the duplication should buy something concrete. If native implementation delivers a meaningful improvement in latency, integration, accessibility or interaction that the web container cannot reasonably provide, that is evidence. “We can build it natively” is not evidence by itself.

Kotlin Multiplatform was interesting, but it solved a later phase

Kotlin Multiplatform naturally entered the conversation. Sharing logic between clients is attractive, especially when Kotlin is already part of the team’s toolbox. But KMP does not automatically transform an existing JavaScript engine ecosystem and a mature web application into common code. Adopting it then would have required a second architectural project: decide which logic to move, which JavaScript pieces remain, how runtimes cooperate and where a new shared domain boundary should live.

That could become a strong investment later. It was not necessarily the best first experiment. This is an important distinction: a technology can be appropriate and still arrive too early. KMP represented a possible long-term structure; Capacitor represented a cheaper way to discover how much of the existing web product was already good enough to become an app.

Postponing KMP did not close the door. It preserved information. If a future layer proves that it should live outside the browser, we can move it with a clearer contract and a better reason.

Capacitor as a boundary, not a permanent destination

Capacitor packages a web application as a mobile application and exposes device capabilities through plugins or native bridges. The simplest description is “a web app inside a native container,” but the architectural value lies in the boundary. Most of the product can remain one application while device-specific needs cross into native code where they are actually useful.

For Blupoli Puzzles, that suggested a straightforward strategy. Engines, catalogue, navigation, content and most of the UI could keep evolving in one place. Storage integrations, sharing, haptics, notifications, system UI or any capability that genuinely depended on the device could sit behind a bridge. The web product did not have to pretend the device was irrelevant; it simply did not need to be rewritten in order to reach it.

The choice also did not commit every puzzle engine to JavaScript forever. If one mechanic later proves that it needs a different implementation for performance or integration reasons, that case can be studied on its own. The first move preserved options instead of closing them.

Text-free diagram where one shared web product crosses a bridge to access several phone capabilities without duplicating its puzzle engines
The proposed structure keeps one product base and treats the native bridge as a narrow boundary for device-specific capabilities.

The decision raised the quality bar for the mobile web

There is an uncomfortable but useful consequence to choosing reuse: you cannot hide a weak web experience behind an app icon. Capacitor packages what you already have. If the board is awkward to touch, it remains awkward. If navigation wastes half the screen, the container does not fix it. If controls depend on hover or precise mouse input, Android simply makes those assumptions more visible.

That turned the mobile strategy into productive pressure on the web. Responsive layout, touch targets, hierarchy, persistence, performance and offline behavior stopped being “nice improvements for phone users” and became prerequisites for any future installed app. Work done in preparation for Android would first improve the browser product.

The connection to the shared UI system is direct. A shell that can reorganize controls, reserve space for the board and maintain coherent states on narrow screens lowers the cost of every future container. The best preparation for an app was not another client. It was making the current client worth reusing.

Touch changes details that desktop quietly forgives

An interface that works with a mouse can still be frustrating under a thumb. Pointer input offers precision and hover. A hand obscures part of the screen, needs larger targets and can trigger accidental scrolling. A gesture that feels obvious on desktop may compete with system navigation or page movement on a phone.

Puzzles amplify these constraints because the interactive surface is often dense. A large grid cannot increase every cell indefinitely. Different mechanics may need different answers: zoom, controlled panning, orientation, input modes, closer toolbars or layout changes. There is no single mobile patch.

Capacitor does not remove that work, and admitting this matters. Code reuse does not guarantee ergonomic reuse. The advantage is that improvements still happen in the main product instead of disappearing into a mobile-specific fork.

Offline behavior and persistence carry different expectations in an installed app

A browser page can lean on connectivity more easily. An installed application creates a stronger expectation: open quickly, keep your session and remain useful when the network disappears. A puzzle product is well positioned to meet that expectation because much of its core logic runs locally, but the behavior still has to be designed.

A reuse-first strategy therefore forces questions about assets, caching, saved sessions, schema changes and updates. Browser storage may be sufficient for some state and not for others. Capacitor can provide stronger storage mechanisms, but the product still needs a clear data model so that state does not end up duplicated across layers.

The principle remains the same: use the bridge where it adds value and avoid moving responsibilities merely because a native option exists. Session persistence belongs to the product. The physical storage mechanism can vary by environment.

Performance should be measured before it is rewritten

“Native will be faster” can be true for a specific task and too vague to justify an architecture. Puzzle engines have different performance profiles. Some spend most of their cost generating or solving. Others render a light board and wait for human interaction. Before moving logic, we need to know where the actual bottleneck lives.

Modern browsers can do a lot, but they still have limits. A heavy generator can block the main thread if badly structured. A large DOM or canvas may need work. The right answer could be a Web Worker, a better algorithm, a generation budget, a different rendering approach or—in a real measured case—native code. The key is that the decision follows evidence.

This is consistent with our engine architecture. As we explain in the article about puzzle engines, we do not force one algorithm onto every mechanic. We do not need one platform answer for every performance problem either.

A mobile app should not become an excuse to duplicate brand and content

The product is larger than the board. Names, descriptions, categories, help, onboarding, interface copy and editorial links all evolve. If a native app keeps manual copies of that information, each content change gains another opportunity to drift. Localization becomes especially painful because the duplicated surface multiplies across languages.

Keeping one web product for the first mobile version means one catalogue and one structured content model continue to power the experience. If a deeper native UI appears later, it should consume stable contracts and data rather than begin with copied files. Capacitor postpones that duplication until we have stronger boundaries and a real reason to create another presentation layer.

This matters even more as localization expands. A new string should not need to travel through two clients and several locales along unrelated workflows. Mobile architecture has to respect content architecture, not create a competing one.

The native bridge should stay small and deliberate

Hybrid architectures have a failure mode of their own: the bridge can become a junk drawer. Whenever something feels slightly awkward on the web, another plugin appears. Eventually the application depends on a large custom native surface and the original reuse advantage disappears. That is why the boundary needs criteria.

A capability is a good bridge candidate when it genuinely depends on the device, clearly improves the Android experience and can be isolated behind a stable interface. Sharing to other apps, system integration or haptic feedback are reasonable examples. Moving a generator solely because “Android can run native code” requires a much stronger case.

A narrow bridge is also easier to test. Web can use mocks or fallbacks; Android can provide adapters. A wide bridge tends to mix product domain, UI behavior and operating-system APIs until the layers are hard to reason about.

Publishing to an app store introduces obligations the web does not have

Reaching Android does not end when an APK builds. There is release management, versioning, permissions, package size, back navigation, lifecycle, icons, privacy and update expectations. Capacitor reduces duplication of the product, but it does not remove platform work.

This distinction protects us from unrealistic promises. The goal was never “turn the website into an app in one afternoon.” The goal was a path where the additional cost stays concentrated in things that genuinely belong to Android. The stronger the web foundation becomes, the smaller that Android-specific delta can be.

It also supports staged learning: package the product, test real devices, measure touch and performance problems, solve integration issues, then decide whether any piece deserves a different architecture.

Reversibility was part of the design criteria

The riskiest architectural decisions during a learning phase are those that require paying the entire cost before producing useful information. A full rewrite could be excellent and still prevent us from discovering whether it was necessary. A hybrid wrapper is a more reversible experiment: if it works, we avoid massive duplication; if it fails in particular areas, those areas become obvious candidates for another solution.

Reversibility does not mean a lack of commitment. We can commit to delivering a strong Android experience while refusing to decide in advance that every engine must migrate. Separating the product goal from the implementation decision is useful far beyond mobile development.

The monorepo makes the idea of separate products with shared code more explicit

As the project evolved into Blupoli, the repository architecture moved toward a monorepo where the main web, Puzzles and future products can live as distinct applications while sharing packages and conventions. That structure fits the philosophy behind the Capacitor choice: avoid one giant monolith, but do not copy common capabilities by hand either.

A future Android app can have its own surface and release cycle without forcing puzzle engines or catalogue data to be duplicated. Capacitor is one option inside that structure, not the center of it. The durable principle is broader: share what is truly shared and keep product boundaries clear enough that implementation can evolve.

The web audit had to happen before we asked the same foundation to support another platform

At the same time we were discussing Android, we were starting a broad audit of UX, UI, performance and architecture. That mattered because packaging an inconsistent foundation merely distributes the inconsistency. Before adding another public surface, we needed to strengthen the product already in use.

The reasoning is developed in “Stopping feature work to audit what we had already built”. Responsive behavior, themes, controls, catalogue structure, onboarding and visual debt are not separate from Android. They are the foundation every mobile client would depend on.

This order also keeps the motivation clean. We do not improve touch targets only to satisfy a store checklist. We improve them because the web product already needs them. Android benefits as a consequence.

Choosing Capacitor was not choosing between web and native

The phrase “web versus native” is too binary for a hybrid architecture. We can have a web UI and native services. We can keep JavaScript engines and add a device integration. We can move part of the domain later without rewriting navigation. We could even end up with a deeper native presentation if evidence eventually supports it.

The actual choice was to avoid drawing an unnecessarily large platform boundary too early. The smaller the platform-specific layer is, the easier it is to preserve coherence and the clearer it becomes when that layer deserves to grow. Architecture can then respond to observed problems instead of an abstract preference for purity.

The best rewrite is the one you can justify with evidence

Rewrites are seductive because they promise to eliminate debt in one move. They also erase knowledge encoded in small, accumulated details. Blupoli Puzzles’ web surface contains many such decisions: how a board responds, which metadata the catalogue expects, how articles connect, what a saved session contains, how themes and navigation interact.

Recreating all of that may eventually be the right move, but it deserves a cause. If future mobile requirements prove that a different architecture materially improves something fundamental and the cost of maintaining the hybrid approach exceeds the cost of migration, that would be evidence. Until then, our goal is to learn cheaply.

That is what “Capacitor first” means. It does not mean “Capacitor forever.” It means the first mobile version should preserve as much of the existing product as possible, reveal its real limits, and let the second decision be better because it is based on behavior rather than assumptions.

A useful mobile strategy improves the product before the app even exists

Perhaps the most valuable consequence of the decision is that it made us inspect the web with an application mindset. Does it open quickly? Does it preserve state? Are touch controls comfortable? Does layout survive a narrow viewport? Is navigation coherent? Can useful parts work without a connection? Are device capabilities separated behind clear boundaries?

Answering those questions improves Blupoli Puzzles even for people who never install anything. That is a healthy sign: the work remains valuable even if the platform plan later changes.

In 2026 we did not need to predict the perfect client for the next several years. We needed a way to reach Android without prematurely splitting the team, the codebase and the experience. Capacitor provided that bridge. The lasting lesson is less about the name of the tool and more about a design habit that still guides Blupoli: reuse first, measure the limits, and rewrite only the parts you can clearly explain why they deserve to be different.