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-gradient and 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:

  1. Native Performance: Using @react-native-firebase on Android/iOS for deep system integration.
  2. 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 with expo prebuild.
  • AuthService.web.ts: Uses standard firebase/auth. It initializes the app with a firebaseConfig.ts object.

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-signin to 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/Roumi had one copy, and packages/ui had another. React Context explodes if you try to cross these boundaries.
  • The Fix: I had to enforce a Singleton pattern for React.
    • I used overrides in the root package.json to force every package to use exactly react@19.1.0.
    • I corrected metro.config.js to properly resolve react and react-native to the app’s node_modules.
// 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-screens version mismatch. expo-router depended on a newer version (4.19.0) than what was installed in my app (4.16.0).
  • The Fix: Another entry in overrides to 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.Image component 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.