Welcome to the first devlog for Roumi, the app that transforms videos into viral text. I just wrapped up Epic 1: Onboarding & Identity, and I want to take you through my journey, from defining a unique artistic direction to solving complex cross-platform engineering challenges.
šØ The Artistic Direction: Making SaaS “Cosy”
When I started Roumi, I knew I didn’t want another cold, sterile utility app. AI can feel impersonal, so my goal was to inject warmth and personality into the core experience.
The “Warm” Design System
I built a custom design system centered around:
- Palette: Soft peaches, warm neutrals, and vibrant primaries, moving away from standard “tech blue”.
- Glassmorphism: I used
expo-linear-gradientand transparent backgrounds to create a “Direct-on-Glass” aesthetic. Inputs and buttons float on top of a vibrant world, rather than sitting in rigid white boxes. - Micro-Interactions: Soft shadows and rounded corners (
borderRadius: 22) make every element feel touchable.
// packages/ui/src/components/atoms/ThemedInput.tsx
const styles = StyleSheet.create({
inputWrapper: {
backgroundColor: colors.neutral.surface, // slightly transparent
borderWidth: 1.5,
borderColor: colors.neutral.border,
borderRadius: 22, // Rounder = Friendlier
...colors.shadows.soft, // Custom shadow token
},
// ...
});
The Mascot
Identity isn’t just a logo. I introduced a dynamic Mascot component that reacts to the user state. It’s not just decoration; it’s a companion that guides you through the onboarding flow, making the “boring” parts (like signing up) feel like part of the story.
š ļø The Tech Stack: Monorepo Power
I chose a robust, scalable architecture from day one:
- Repo: Turbo Monorepo with
pnpm/npm workspaces. - Packages:
apps/Roumi: The Expo (React Native) application.packages/ui: My shared “Atomic” component library.packages/business-logic: The brain of the app, containing hooks, services, and types.
This separation forces me to keep my code clean. The UI doesn’t know about Firebase, and the Business Logic doesn’t know about View or Text.
š The Challenge: Cross-Platform Authentication
The hardest engineering hurdle in this Epic was Authentication. I wanted the best of both worlds:
- Native Performance: Using
@react-native-firebaseon Android/iOS for deep system integration. - Web Compatibility: Running the same app in the browser using the Firebase JS SDK.
The “Default App” Crash
Early on, I hit a wall. Using @react-native-firebase on the web caused the infamous error:
No Firebase App '[DEFAULT]' has been created
This happens because the Native SDK tries to bind to iOS/Android binaries that don’t exist in a browser.
The Solution: Platform-Specific Extensions
I solved this by leveraging React Native’s platform resolution. I split my AuthService into two files:
AuthService.native.ts: Uses@react-native-firebase/auth. It talks directly to the native code I built withexpo prebuild.AuthService.web.ts: Uses standardfirebase/auth. It initializes the app with afirebaseConfig.tsobject.
My consumer code just imports AuthService, and Metro bundler magically picks the right one.
// packages/business-logic/src/auth/AuthService.native.ts
import auth from '@react-native-firebase/auth';
export class AuthService {
static async signInWithGoogle() {
// Native: Get ID token from Google, then credential
const { idToken } = await GoogleSignin.signIn();
const credential = auth.GoogleAuthProvider.credential(idToken);
return auth().signInWithCredential(credential);
}
}
// packages/business-logic/src/auth/AuthService.web.ts
import { signInWithPopup, GoogleAuthProvider } from 'firebase/auth';
export class AuthService {
static async signInWithGoogle() {
// Web: Just pop it up!
return signInWithPopup(auth, new GoogleAuthProvider());
}
}
This keeps the consumer code completely clean:
// apps/Roumi/app/login.tsx
import { AuthService } from '@Roumi/business-logic';
// Works on iOS, Android, and Web!
await AuthService.signInWithGoogle();
ā” Google Sign-In & Architecture
I didn’t stop at email/password. I implemented Google Sign-In with a future-proof architecture.
- Native: I use
@react-native-google-signin/google-signinto get an ID Token, then exchange it for a Firebase Credential. - Web: I simply use
signInWithPopup(auth, provider).
This modular approach means adding Apple Sign-In in the future effectively follows the exact same pattern, just a new provider, handled natively on iOS and via popup on Web.
The Debugging Journey: Monorepo & Native Headaches
It wasn’t all smooth sailing. Getting a modern Turbo Monorepo to play nicely with Native Modules and the React Native New Architecture led to some hair-pulling moments.
1. The “Invalid Hook Call” Mystery
The most persistent issue was the dreaded Invalid hook call error.
- The Cause: In a monorepo, it’s easy to end up with multiple copies of React. My
apps/Roumihad one copy, andpackages/uihad another. React Context explodes if you try to cross these boundaries. - The Fix: I had to enforce a Singleton pattern for React.
- I used
overridesin the rootpackage.jsonto force every package to use exactlyreact@19.1.0. - I corrected
metro.config.jsto properly resolvereactandreact-nativeto the app’snode_modules.
- I used
// package.json
{
"overrides": {
"react": "19.1.0",
"react-dom": "19.1.0",
"react-native": "0.81.5"
}
}
2. Native Crashes & The New Architecture
I also encountered a cryptic ClassCastException on Android during startup.
- The Culprit:
react-native-screensversion mismatch.expo-routerdepended on a newer version (4.19.0) than what was installed in my app (4.16.0). - The Fix: Another entry in
overridesto force the entire monorepo to align on a single, compatible version.
3. Animated.Image vs. New Architecture
Finally, my custom Mascot component was crashing the app with a String cannot be cast to Boolean error.
- The Discovery: The legacy
Animated.Imagecomponent has known compatibility issues with React Native’s Fabric renderer (New Architecture). - The Evolution: I refactored the component to use
expo-image. Not only did this fix the crash, but it gave us way better performance and caching for our mascot assets.
š What’s Next?
With a solid identity layer and a beautiful visual foundation in place, I am ready for Epic 2: The Feed.
I’m going to build the core loop: swiping through content, transforming video to text, and making magic happen.
Stay tuned.