Multi-Platform Finish: 4K, iPad, and Feature Flags
Photo: Unsplash
The last two weeks before a launch are no time for new features. They're the time when you discover the app looks weird on an iPad Pro in landscape, that on a 4K monitor the width isn't constrained and the text runs across the entire screen, and that iOS devices with a home indicator at the bottom of the screen need extra treatment. This week is an honesty report about the final polish.
Responsive design across three breakpoint levels
OutaStory runs on six platforms: web, macOS, Windows, iPadOS, iOS, Android. Those are different form factors, and CSS responsive design has to cover all of them.
We defined three maximum widths:
- 1440px — standard desktop
- 1920px — full-HD monitor
- 2560px — 4K / ultra-wide
The content container is resized at each of these breakpoints. On 4K monitors without this constraint, a body-text paragraph would stretch to 50 to 60 characters wide, which massively hurts readability. The maximum width isn't a cosmetic restriction — it's a readability decision.
On top of that come the usual small breakpoints for mobile devices. On mobile, for example, the "Write" button on the /my-stories page is hidden. Not because you can't write on a phone, but because the primary writing flow works better on tablet and desktop. The hiding is explicit, not layout magic.
CategoryTabs and desktop wrapping
The category navigation on desktop cost us some time. The category tabs display all root categories horizontally. On smaller screens they all fit side by side. On wide screens, an ugly horizontal scroll starts appearing past a certain point.
The solution: on desktop breakpoints, the tabs wrap into two rows when they don't all fit in one. That sounds simple, but it's not trivial in CSS — flex-wrap alone isn't enough when you want the first row to always be fully filled and the second row to appear centered underneath it.
We combined this with a tooltip system that's purely CSS-based: data-tooltip attributes with ::before/::after pseudo-elements. No JavaScript, no external library. The hover tooltip shows the full category name when the tab is too narrow to display it in full.
iPad Pro in landscape
The iPad Pro in landscape was the single biggest chunk of work in the multi-platform effort. The device has a resolution of 2732×2048 points at 264 PPI — it's practically a small 4K monitor.
The problem: Blazor MAUI on iPadOS renders inside a WebView that accounts for the device pixel ratio. But certain CSS properties that scale correctly in a web browser behave differently in the MAUI WebView. This mostly affects font sizes in rem, which scale correctly in Edge on Windows but get interpreted slightly differently in the iOS WebView.
The fix wasn't a one-size-fits-all solution but a careful overhaul of the layout components with explicit min-height and max-width definitions that stay consistent across all contexts.
Safe area: env(safe-area-inset-bottom)
iOS and Android both have an area at the bottom of the screen occupied by the home indicator or the navigation bar. If you place an app's bottom navigation there, buttons disappear beneath the home indicator.
The CSS function env(safe-area-inset-bottom) solves this: it returns the distance the content needs to be shifted upward to remain visible. On devices without a home indicator (for example, an iPad with a keyboard), it returns 0px — so no unwanted spacing.
OutaStory's bottom navigation uses padding-bottom: calc(var(--os-space-md) + env(safe-area-inset-bottom)) — our own spacing token plus the safe-area value. That works correctly on every iOS and Android device we tested.
Photo: Unsplash
Feature flags: rollout strategy
The three big new features — AI cover generation, audio generation, and the story-ads opt-out — are all behind feature flags. That enables a controlled rollout.
The feature flags are configured via featureflags.json, a file populated differently per host and per deployment environment. The three relevant flags:
AiCoverGeneration— enables the AI cover step in the publish wizardAudioGeneration— enables the audio step in the publish wizardStoryAdsOptOut— shows the opt-out toggle in the publish wizard
At relaunch, all three flags are set to true — for web, macOS, Windows, iOS, iPadOS, and Android. But the architecture lets us disable individual flags on individual hosts. If Azure quotas are exhausted in a region, we can disable AiCoverGeneration for that region without affecting the other features.
New authors: automatic author-record creation
A small detail, but an important one for the onboarding experience: when a new user registers and creates their first draft, an author record is automatically created for them.
That sounds like a given, but it's a concrete step that had to be triggered manually in the first version. Now it happens implicitly on the first draft save — transparent to the author, with no extra "create profile" form. She starts writing, and the rest falls into place.
What the last two weeks taught us
If I'm honest: the last two weeks are the most exhausting, because every problem is small and yet important. A button positioned wrong on an iPad landscape screen isn't a critical bug. But it's the first thing a new user sees when they open the app for the first time.
The time you invest in this phase isn't glamorous. But it's the difference between a product that's "good enough" and a product that feels thought through.
What's next?
Tomorrow is launch day. The main announcement post goes live at the same time as the relaunch on www.outastory.com. We're looking forward to everyone who writes, reads, and listens with us.
