Files
voicebox/mobile/app/_layout.tsx
T
James Pine f4d21504e3 Mobile companion app + paired-device backend
New iOS-first companion (Expo SDK 54 + NativeWind v4) with three tabs:
Captures (the hero — floating gold mic, live mic-meter waveform,
expand-row playback), Generate (profile picker + speak + autoplay +
recent), and Voices (searchable profile list).

Pairing (V0): backend mints a one-time token, mobile scans/pastes the
voicebox:// URL, server returns a long-lived bearer it stores only as a
SHA-256 hash. Bearer-or-loopback auth applied to every user-data router
so binding 0.0.0.0 doesn't leak existing endpoints. Loopback callers
(the desktop app) keep their friction-free access.

Desktop Settings → Mobile pane: live host picker (LAN / Tailscale auto-
detected via the App-bundle binary path on macOS), QR rendering,
5-minute expiry countdown, copyable URL fallback, paired-device list
with revoke. Auto-closes when a new device pairs.

just dev now binds the backend to 0.0.0.0 so paired phones can reach
it — and just setup-python pins mlx-audio==0.4.1 + mlx-lm so fresh
Apple Silicon worktrees get a working STT path on first install.
2026-04-25 17:09:32 -07:00

47 lines
1.3 KiB
TypeScript

import '../global.css';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { Stack } from 'expo-router';
import { StatusBar } from 'expo-status-bar';
import { useEffect, useState } from 'react';
import { GestureHandlerRootView } from 'react-native-gesture-handler';
import { SafeAreaProvider } from 'react-native-safe-area-context';
import { colors } from '@/lib/colors';
import { useSession } from '@/lib/session';
export default function RootLayout() {
const hydrate = useSession((s) => s.hydrate);
const [queryClient] = useState(
() =>
new QueryClient({
defaultOptions: {
queries: {
staleTime: 5_000,
retry: 1,
},
},
}),
);
useEffect(() => {
void hydrate();
}, [hydrate]);
return (
<QueryClientProvider client={queryClient}>
<GestureHandlerRootView style={{ flex: 1, backgroundColor: colors.background }}>
<SafeAreaProvider>
<StatusBar style="light" />
<Stack
screenOptions={{
headerShown: false,
contentStyle: { backgroundColor: colors.background },
animation: 'fade',
}}
/>
</SafeAreaProvider>
</GestureHandlerRootView>
</QueryClientProvider>
);
}