Quando Features Novas Invadem Sistemas Antigos
Uma falha recorrente em apps React Native feitas com vibe coding é quando uma feature nova importa um service existente diretamente, mexe em state que não é dela e quebra um fluxo sem relação. Auth costuma ser a vítima porque tende a virar a dependência compartilhada de todo mundo.
Em apps montadas rapidamente com Cursor e Claude Code, cada feature é gerada em contexto local. O código compila. Pode até passar no happy path. Mas, sem boundaries de service, arquivos independentes falham juntos.
Esse é o problema fundamental do vibe coding em escala. Assistentes de código com IA são arquitetos cegos para contexto. Eles geram o que você pede, não o que a sua codebase precisa. Sem fronteiras explícitas de service, cada feature nova é uma potencial breaking change esperando para acontecer.
A Regra da Interface
Aqui está a regra: todo service da sua app expõe uma interface. A interface mora na sua codebase. A implementação, seja Supabase, Firebase, RevenueCat ou um mock, fica atrás dessa interface. O resto da app não sabe qual delas está em uso.
// src/services/auth/auth.interface.ts
// Your app owns this. Not Supabase. Not Firebase. You.
export interface User {
id: string
email: string
displayName: string | null
avatarUrl: string | null
}
export interface Session {
accessToken: string
refreshToken: string
expiresAt: number
user: User
}
export interface AuthService {
signIn(credentials: EmailCredentials): Promise<Session>
signOut(): Promise<void>
getSession(): Promise<Session | null>
onAuthStateChange(
callback: (session: Session | null) => void
): () => void
}
Essa é a costura. Tudo do lado da app fala com AuthService. Tudo do lado do SDK implementa essa interface. Quando você quiser trocar Supabase por Firebase, vai escrever uma nova classe de implementação. A tela de login não muda. Os route guards não mudam. Os hooks não mudam. O Claude Code nem precisa saber que a troca aconteceu.
O Composition Root
Um arquivo constrói o grafo inteiro de dependências. Em qualquer outro lugar, você só consome.
// src/services/create-services.ts
// ONE file. Cursor never touches this. You own it.
import { config } from '@/constants/config'
import { SupabaseAuthService } from './auth/supabase'
import { RevenueCatService } from './payments/revenuecat'
export type Services = ReturnType<typeof createServices>
export function createServices() {
const storage = createStorageServices(config.storageMode)
return {
storage,
auth: new SupabaseAuthService(storage.secureStorage),
payments: new RevenueCatService(),
analytics: new SegmentAnalyticsService(),
crashReporting: new SentryService(),
network: createNetworkService(),
}
}
Quer trocar Supabase por Firebase? Mude uma linha. Quer sair de Segment para PostHog? Uma linha. É assim que uma arquitetura React Native escalável se parece quando você faz vibe coding com ajuda de IA.
Por que Isso Importa Ainda Mais em Apps Codados com IA
Quando um humano escreve código, ele carrega a arquitetura na cabeça. Sabe que não deve importar supabase diretamente dentro de um componente de tela. Assistentes de código com IA não carregam arquitetura na cabeça. Eles carregam janelas de contexto. E janelas de contexto esquecem.
// src/context/auth-provider.tsx
// This provider doesn't know what auth SDK you use.
// Claude Code generates against the interface, never the implementation.
export function AuthProvider({ children }: PropsWithChildren) {
const { auth } = useServices()
const [session, setSession] = useState<Session | null>(null)
useEffect(() => {
auth.getSession().then(setSession)
return auth.onAuthStateChange(setSession)
}, [auth])
const signIn = useCallback(
async (creds: AuthCredentials) => {
const s = await auth.signIn(creds)
setSession(s)
},
[auth]
)
return (
<AuthContext.Provider
value={{ session, user: session?.user ?? null, signIn }}>
{children}
</AuthContext.Provider>
)
}
Esse arquivo não mudou em três migrações diferentes de backend. Ele não sabe como supabase.auth.signInWithPassword funciona. Ele conhece auth.signIn(). Esse é o ponto.
O Autotomy Expo Starter Pack já vem com essa arquitetura embutida: interfaces de service, composition root, barrel exports, tudo conectado para que Cursor e Claude Code gerem código respeitando as suas fronteiras desde o primeiro dia.