← All writing
Product & Ownership · Feb 2024

No product manager. No designer. No spec. Just a web app and a question: what should the mobile app be?

Building a product nobody defined for you

The problem

When I joined S.ID, the mobile app did not exist. There was a web product — a link shortener and microsite builder — and a decision that it needed a mobile counterpart. That was the full brief.

No product manager. No UI/UX designer. No mobile team. The only reference was the web version and a handful of competitor apps I was asked to look at.

Most engineers in this situation wait for someone to hand them a spec. I didn't have that option. So I became the person who wrote it.

The first few weeks weren't about code. They were about understanding the product well enough to make decisions nobody had made yet: What does a link shortener actually need on mobile that a website doesn't? What does a microsite builder look like on a 390px screen? What do we build first, and what do we defer?

The only person I could pressure-test ideas with was a fullstack developer who knew the existing web product well. Everything else — discovery, prioritization, architecture, design — I owned alone.


The decisions

Wireframe before building — validate cheaply

Without a designer and without a product team, the risk of building the wrong thing was high. The solution wasn't to just start coding and iterate — that's expensive. The solution was to create low-fidelity wireframes first, get validation from the founding team, and only then build.

This felt slow at the start. It wasn't. The wireframing process surfaced several assumptions that would have cost significant rework if discovered after implementation. A two-day wireframe that gets rejected is far cheaper than a two-week feature that gets redesigned.

The web version became the visual reference for structure and content — but not for interaction. Mobile has different affordances, different thumb zones, different session lengths. I had to make deliberate decisions about what to adapt and what to redesign entirely.

Melos monorepo from day one — building for white label

The business had a long-term goal: white label. The same app, rebrandable and deployable for different clients under different names. This wasn't an immediate requirement, but it was a known future direction.

Building a monolith and refactoring it for white label later is painful. Building for it from the start is an upfront investment that pays off. So from day one, I structured the codebase as a Melos monorepo with separated packages:

  • uikit — all visual components, themed and swappable
  • translation — S.ID had its own translation system, extracted as a standalone package
  • storage — abstracted local storage layer
  • shared — utilities and models used across features
  • Forked packages — where third-party packages needed custom modifications, I forked them rather than patching in-place

This meant every visual and behavioral layer was independently replaceable. White label becomes a configuration concern, not an architectural one.

The same structure also accelerated day-to-day development significantly — once packages were established, adding features was faster because shared infrastructure already existed.

GetX over BLoC — matching the tool to the context

The choice comes down to context. BLoC enforces strict unidirectional data flow — valuable when multiple developers work on the same codebase and predictability matters more than speed. Alone, that overhead compounds: events, states, blocs, mappers — all written by one person, for one person.

GetX cuts that overhead significantly. With one developer and a focused product, the trade-off was straightforward: ship faster, accept slightly less formal structure. The structure was still there — it just wasn't enforced by the framework.

Freezed for the microsite builder — taming component complexity

The microsite builder was the hardest feature to model. A microsite is essentially a website builder: users compose pages from components — text blocks, image sections, buttons, forms, social links, and more. Each component type has a completely different set of fields and a completely different UI.

The naive approach is a massive if-else or switch statement mapping component types to widgets. This becomes unmanageable quickly — every new component type touches the same branching logic.

I used freezed to model each component as a sealed union type. Each variant carries only the fields it needs. The UI layer uses map / when to exhaustively handle each variant — the compiler enforces that every component type is handled, and adding a new component type produces a compile error everywhere it isn't handled yet.

The result: adding a new component type is a localized change. Define the variant, add the fields, handle it in the UI. No hunting through if-else chains.

The sheet-based UI was its own challenge. S.ID's interaction model relies heavily on bottom sheets — component editors, settings panels, previews. The available packages weren't flexible enough for what the design needed, so I forked smooth_sheet and modified it to support the specific behaviors required.

Own the full cycle — including reporting

Shipping the app was not the end of the responsibility. I set up Crashlytics, monitored analytics, received feedback from the marketing team, and produced weekly and monthly reports for leadership.

This was not in the original scope. But understanding how users actually behave in the app — where they drop off, what crashes, what features they use — is the difference between shipping and building. I treated the post-launch data as part of the job, not an afterthought.


The trade-offs

White label preparation added upfront complexity. The monorepo structure with separated packages takes more time to set up than a single-package app. For the first month, it felt like overhead. By month three, it was clearly the right call — the shared infrastructure was paying for itself.

GetX has limits at scale. If S.ID's mobile team had grown to three or four developers, I would have reconsidered the state management choice. GetX's implicit dependencies become harder to trace as the codebase grows. The decision was right for the context it was made in — one developer, one product — but it's not a choice I'd make universally.

iOS didn't ship while I was there. Android launched and reached 5K+ downloads. The iOS version was in progress but blocked by an App Store requirement: Apple mandates in-app purchase implementation for apps with subscription content, which required additional development. I left before that was resolved — the mobile product was subsequently postponed.


The outcome

  • Android app on Play Store, 5K+ downloads
  • Melos monorepo with separated packages, ready for white label
  • Microsite builder with sealed union component model — exhaustive pattern matching, zero if-else chains for component type handling
  • End-to-end ownership: discovery, wireframing, architecture, development, store submission, analytics, reporting

The most important thing this project demonstrated is not any single technical decision. It's that I can walk into an undefined situation, figure out what needs to be built, build it correctly, and take responsibility for what happens after it ships.

That's a different skill from being handed a spec and executing it well.


What I'd do differently

Start the iOS build in parallel with Android from the beginning. The App Store's in-app purchase requirement was a known constraint — treating iOS as a follow-on rather than a simultaneous target meant the requirement surfaced late. Earlier iOS development would have surfaced it earlier, giving more time to address it.

Other Writing