Files
timmy-tower/artifacts/mobile/app/(tabs)/_layout.tsx

113 lines
4.5 KiB
TypeScript
Raw Normal View History

feat(mobile): scaffold Expo mobile app for Timmy with Face/Matrix/Feed tabs Task #42 — Timmy Harness: Expo Mobile App ## What was built - New Expo artifact at artifacts/mobile, slug `mobile`, preview path `/mobile/` - Three-tab bottom navigation (Face, Matrix, Feed) — NativeTabs with liquid glass on iOS 26+ - Dark wizard theme (#0A0A12 background, #7C3AED accent) ## WebSocket context (context/TimmyContext.tsx) - Full WebSocket connection to /api/ws with exponential backoff reconnect (1s→30s cap) - Sends visitor_enter handshake on connect, handles ping/pong - Derives timmyMood from agent_state events (idle/thinking/working/speaking) - recentEvents list capped at 100 - sendVisitorMessage() sets mood to "thinking" immediately on send (deterministic waiting state) - speaking mood auto-reverts after estimated TTS duration ## Face tab (app/(tabs)/index.tsx) - Animated 2D wizard face via react-native-svg (hat, head, beard, eyes, pupils, mouth arc, magic orb) - AnimatedPupils: pupilScaleAnim drives actual rendered pupil Circle radius (BASE_PUPIL_R * scale) - AnimatedEyelids: eyeScaleYAnim drives top eyelid overlay via Animated.Value listener - AnimatedMouth: smileAnim + mouthOscAnim combined; SVG Path rebuilt on each frame via listener - speaking mood: 1Hz mouth oscillation via Animated.loop; per-mood body bob speed/amplitude - @react-native-voice/voice installed and statically imported; Voice.onSpeechResults wired properly - startMicPulse/stopMicPulse declared before native voice useEffect (correct hook order) - Web Speech API typed with SpeechRecognitionWindow local interface (zero `any` casts) - sendVisitorMessage() called on final transcript (also triggers thinking mood immediately) - expo-speech TTS speaks Timmy's chat replies on native ## Matrix tab (app/(tabs)/matrix.tsx) - URL normalization: strips existing protocol, uses http for localhost, https for all other hosts - Full-screen WebView with loading spinner and error/retry state; iframe fallback for web ## Feed tab (app/(tabs)/feed.tsx) - FlatList<WsEvent> with proper generics; EventConfig discriminated union (Feather|MaterialCommunityIcons) - Icon names typed via React.ComponentProps["name"] (no `any`) - Color-coded events; event count in header; empty state with connection-aware message ## Type safety - TypeScript typecheck passes with 0 errors - No `any` casts anywhere in new code ## Deviations - expo-av removed (not used; voice input handled via @react-native-voice/voice + Web Speech API) - expo-speech/expo-av NOT in app.json plugins (no config plugins — causes PluginError if listed) - app.json extra.apiDomain added for env-driven domain configuration per requirement - expo-speech pinned ~14.0.8, react-native-webview 13.15.0 for Expo SDK 54 compat - artifact.toml ensurePreviewReachable removed (Expo uses expo-domain router) - @react-native-voice/voice works in Expo Go Android; iOS needs native build (graceful fallback) Replit-Task-Id: 0748cbbf-7b84-4149-8fc0-9d697287a0e6
2026-03-19 23:55:16 +00:00
import { BlurView } from "expo-blur";
import { isLiquidGlassAvailable } from "expo-glass-effect";
import { Link, Tabs, router } from "expo-router";
feat(mobile): scaffold Expo mobile app for Timmy with Face/Matrix/Feed tabs Task #42 — Timmy Harness: Expo Mobile App ## What was built - New Expo artifact at artifacts/mobile, slug `mobile`, preview path `/mobile/` - Three-tab bottom navigation (Face, Matrix, Feed) — NativeTabs with liquid glass on iOS 26+ - Dark wizard theme (#0A0A12 background, #7C3AED accent) ## WebSocket context (context/TimmyContext.tsx) - Full WebSocket connection to /api/ws with exponential backoff reconnect (1s→30s cap) - Sends visitor_enter handshake on connect, handles ping/pong - Derives timmyMood from agent_state events (idle/thinking/working/speaking) - recentEvents list capped at 100 - sendVisitorMessage() sets mood to "thinking" immediately on send (deterministic waiting state) - speaking mood auto-reverts after estimated TTS duration ## Face tab (app/(tabs)/index.tsx) - Animated 2D wizard face via react-native-svg (hat, head, beard, eyes, pupils, mouth arc, magic orb) - AnimatedPupils: pupilScaleAnim drives actual rendered pupil Circle radius (BASE_PUPIL_R * scale) - AnimatedEyelids: eyeScaleYAnim drives top eyelid overlay via Animated.Value listener - AnimatedMouth: smileAnim + mouthOscAnim combined; SVG Path rebuilt on each frame via listener - speaking mood: 1Hz mouth oscillation via Animated.loop; per-mood body bob speed/amplitude - @react-native-voice/voice installed and statically imported; Voice.onSpeechResults wired properly - startMicPulse/stopMicPulse declared before native voice useEffect (correct hook order) - Web Speech API typed with SpeechRecognitionWindow local interface (zero `any` casts) - sendVisitorMessage() called on final transcript (also triggers thinking mood immediately) - expo-speech TTS speaks Timmy's chat replies on native ## Matrix tab (app/(tabs)/matrix.tsx) - URL normalization: strips existing protocol, uses http for localhost, https for all other hosts - Full-screen WebView with loading spinner and error/retry state; iframe fallback for web ## Feed tab (app/(tabs)/feed.tsx) - FlatList<WsEvent> with proper generics; EventConfig discriminated union (Feather|MaterialCommunityIcons) - Icon names typed via React.ComponentProps["name"] (no `any`) - Color-coded events; event count in header; empty state with connection-aware message ## Type safety - TypeScript typecheck passes with 0 errors - No `any` casts anywhere in new code ## Deviations - expo-av removed (not used; voice input handled via @react-native-voice/voice + Web Speech API) - expo-speech/expo-av NOT in app.json plugins (no config plugins — causes PluginError if listed) - app.json extra.apiDomain added for env-driven domain configuration per requirement - expo-speech pinned ~14.0.8, react-native-webview 13.15.0 for Expo SDK 54 compat - artifact.toml ensurePreviewReachable removed (Expo uses expo-domain router) - @react-native-voice/voice works in Expo Go Android; iOS needs native build (graceful fallback) Replit-Task-Id: 0748cbbf-7b84-4149-8fc0-9d697287a0e6
2026-03-19 23:55:16 +00:00
import { Icon, Label, NativeTabs } from "expo-router/unstable-native-tabs";
import { SymbolView } from "expo-symbols";
import { Feather, MaterialCommunityIcons, Ionicons } from "@expo/vector-icons";
feat(mobile): scaffold Expo mobile app for Timmy with Face/Matrix/Feed tabs Task #42 — Timmy Harness: Expo Mobile App ## What was built - New Expo artifact at artifacts/mobile, slug `mobile`, preview path `/mobile/` - Three-tab bottom navigation (Face, Matrix, Feed) — NativeTabs with liquid glass on iOS 26+ - Dark wizard theme (#0A0A12 background, #7C3AED accent) ## WebSocket context (context/TimmyContext.tsx) - Full WebSocket connection to /api/ws with exponential backoff reconnect (1s→30s cap) - Sends visitor_enter handshake on connect, handles ping/pong - Derives timmyMood from agent_state events (idle/thinking/working/speaking) - recentEvents list capped at 100 - sendVisitorMessage() sets mood to "thinking" immediately on send (deterministic waiting state) - speaking mood auto-reverts after estimated TTS duration ## Face tab (app/(tabs)/index.tsx) - Animated 2D wizard face via react-native-svg (hat, head, beard, eyes, pupils, mouth arc, magic orb) - AnimatedPupils: pupilScaleAnim drives actual rendered pupil Circle radius (BASE_PUPIL_R * scale) - AnimatedEyelids: eyeScaleYAnim drives top eyelid overlay via Animated.Value listener - AnimatedMouth: smileAnim + mouthOscAnim combined; SVG Path rebuilt on each frame via listener - speaking mood: 1Hz mouth oscillation via Animated.loop; per-mood body bob speed/amplitude - @react-native-voice/voice installed and statically imported; Voice.onSpeechResults wired properly - startMicPulse/stopMicPulse declared before native voice useEffect (correct hook order) - Web Speech API typed with SpeechRecognitionWindow local interface (zero `any` casts) - sendVisitorMessage() called on final transcript (also triggers thinking mood immediately) - expo-speech TTS speaks Timmy's chat replies on native ## Matrix tab (app/(tabs)/matrix.tsx) - URL normalization: strips existing protocol, uses http for localhost, https for all other hosts - Full-screen WebView with loading spinner and error/retry state; iframe fallback for web ## Feed tab (app/(tabs)/feed.tsx) - FlatList<WsEvent> with proper generics; EventConfig discriminated union (Feather|MaterialCommunityIcons) - Icon names typed via React.ComponentProps["name"] (no `any`) - Color-coded events; event count in header; empty state with connection-aware message ## Type safety - TypeScript typecheck passes with 0 errors - No `any` casts anywhere in new code ## Deviations - expo-av removed (not used; voice input handled via @react-native-voice/voice + Web Speech API) - expo-speech/expo-av NOT in app.json plugins (no config plugins — causes PluginError if listed) - app.json extra.apiDomain added for env-driven domain configuration per requirement - expo-speech pinned ~14.0.8, react-native-webview 13.15.0 for Expo SDK 54 compat - artifact.toml ensurePreviewReachable removed (Expo uses expo-domain router) - @react-native-voice/voice works in Expo Go Android; iOS needs native build (graceful fallback) Replit-Task-Id: 0748cbbf-7b84-4149-8fc0-9d697287a0e6
2026-03-19 23:55:16 +00:00
import React from "react";
import { Platform, Pressable, StyleSheet, View, useColorScheme } from "react-native";
feat(mobile): scaffold Expo mobile app for Timmy with Face/Matrix/Feed tabs Task #42 — Timmy Harness: Expo Mobile App ## What was built - New Expo artifact at artifacts/mobile, slug `mobile`, preview path `/mobile/` - Three-tab bottom navigation (Face, Matrix, Feed) — NativeTabs with liquid glass on iOS 26+ - Dark wizard theme (#0A0A12 background, #7C3AED accent) ## WebSocket context (context/TimmyContext.tsx) - Full WebSocket connection to /api/ws with exponential backoff reconnect (1s→30s cap) - Sends visitor_enter handshake on connect, handles ping/pong - Derives timmyMood from agent_state events (idle/thinking/working/speaking) - recentEvents list capped at 100 - sendVisitorMessage() sets mood to "thinking" immediately on send (deterministic waiting state) - speaking mood auto-reverts after estimated TTS duration ## Face tab (app/(tabs)/index.tsx) - Animated 2D wizard face via react-native-svg (hat, head, beard, eyes, pupils, mouth arc, magic orb) - AnimatedPupils: pupilScaleAnim drives actual rendered pupil Circle radius (BASE_PUPIL_R * scale) - AnimatedEyelids: eyeScaleYAnim drives top eyelid overlay via Animated.Value listener - AnimatedMouth: smileAnim + mouthOscAnim combined; SVG Path rebuilt on each frame via listener - speaking mood: 1Hz mouth oscillation via Animated.loop; per-mood body bob speed/amplitude - @react-native-voice/voice installed and statically imported; Voice.onSpeechResults wired properly - startMicPulse/stopMicPulse declared before native voice useEffect (correct hook order) - Web Speech API typed with SpeechRecognitionWindow local interface (zero `any` casts) - sendVisitorMessage() called on final transcript (also triggers thinking mood immediately) - expo-speech TTS speaks Timmy's chat replies on native ## Matrix tab (app/(tabs)/matrix.tsx) - URL normalization: strips existing protocol, uses http for localhost, https for all other hosts - Full-screen WebView with loading spinner and error/retry state; iframe fallback for web ## Feed tab (app/(tabs)/feed.tsx) - FlatList<WsEvent> with proper generics; EventConfig discriminated union (Feather|MaterialCommunityIcons) - Icon names typed via React.ComponentProps["name"] (no `any`) - Color-coded events; event count in header; empty state with connection-aware message ## Type safety - TypeScript typecheck passes with 0 errors - No `any` casts anywhere in new code ## Deviations - expo-av removed (not used; voice input handled via @react-native-voice/voice + Web Speech API) - expo-speech/expo-av NOT in app.json plugins (no config plugins — causes PluginError if listed) - app.json extra.apiDomain added for env-driven domain configuration per requirement - expo-speech pinned ~14.0.8, react-native-webview 13.15.0 for Expo SDK 54 compat - artifact.toml ensurePreviewReachable removed (Expo uses expo-domain router) - @react-native-voice/voice works in Expo Go Android; iOS needs native build (graceful fallback) Replit-Task-Id: 0748cbbf-7b84-4149-8fc0-9d697287a0e6
2026-03-19 23:55:16 +00:00
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { Colors } from "@/constants/colors";
function NativeTabLayout() {
return (
<NativeTabs>
<NativeTabs.Trigger name=\"index\">
<Icon sf={{ default: \"face.smiling\", selected: \"face.smiling.fill\" }} />
feat(mobile): scaffold Expo mobile app for Timmy with Face/Matrix/Feed tabs Task #42 — Timmy Harness: Expo Mobile App ## What was built - New Expo artifact at artifacts/mobile, slug `mobile`, preview path `/mobile/` - Three-tab bottom navigation (Face, Matrix, Feed) — NativeTabs with liquid glass on iOS 26+ - Dark wizard theme (#0A0A12 background, #7C3AED accent) ## WebSocket context (context/TimmyContext.tsx) - Full WebSocket connection to /api/ws with exponential backoff reconnect (1s→30s cap) - Sends visitor_enter handshake on connect, handles ping/pong - Derives timmyMood from agent_state events (idle/thinking/working/speaking) - recentEvents list capped at 100 - sendVisitorMessage() sets mood to "thinking" immediately on send (deterministic waiting state) - speaking mood auto-reverts after estimated TTS duration ## Face tab (app/(tabs)/index.tsx) - Animated 2D wizard face via react-native-svg (hat, head, beard, eyes, pupils, mouth arc, magic orb) - AnimatedPupils: pupilScaleAnim drives actual rendered pupil Circle radius (BASE_PUPIL_R * scale) - AnimatedEyelids: eyeScaleYAnim drives top eyelid overlay via Animated.Value listener - AnimatedMouth: smileAnim + mouthOscAnim combined; SVG Path rebuilt on each frame via listener - speaking mood: 1Hz mouth oscillation via Animated.loop; per-mood body bob speed/amplitude - @react-native-voice/voice installed and statically imported; Voice.onSpeechResults wired properly - startMicPulse/stopMicPulse declared before native voice useEffect (correct hook order) - Web Speech API typed with SpeechRecognitionWindow local interface (zero `any` casts) - sendVisitorMessage() called on final transcript (also triggers thinking mood immediately) - expo-speech TTS speaks Timmy's chat replies on native ## Matrix tab (app/(tabs)/matrix.tsx) - URL normalization: strips existing protocol, uses http for localhost, https for all other hosts - Full-screen WebView with loading spinner and error/retry state; iframe fallback for web ## Feed tab (app/(tabs)/feed.tsx) - FlatList<WsEvent> with proper generics; EventConfig discriminated union (Feather|MaterialCommunityIcons) - Icon names typed via React.ComponentProps["name"] (no `any`) - Color-coded events; event count in header; empty state with connection-aware message ## Type safety - TypeScript typecheck passes with 0 errors - No `any` casts anywhere in new code ## Deviations - expo-av removed (not used; voice input handled via @react-native-voice/voice + Web Speech API) - expo-speech/expo-av NOT in app.json plugins (no config plugins — causes PluginError if listed) - app.json extra.apiDomain added for env-driven domain configuration per requirement - expo-speech pinned ~14.0.8, react-native-webview 13.15.0 for Expo SDK 54 compat - artifact.toml ensurePreviewReachable removed (Expo uses expo-domain router) - @react-native-voice/voice works in Expo Go Android; iOS needs native build (graceful fallback) Replit-Task-Id: 0748cbbf-7b84-4149-8fc0-9d697287a0e6
2026-03-19 23:55:16 +00:00
<Label>Timmy</Label>
</NativeTabs.Trigger>
<NativeTabs.Trigger name=\"matrix\">
<Icon sf={{ default: \"cube\", selected: \"cube.fill\" }} />
feat(mobile): scaffold Expo mobile app for Timmy with Face/Matrix/Feed tabs Task #42 — Timmy Harness: Expo Mobile App ## What was built - New Expo artifact at artifacts/mobile, slug `mobile`, preview path `/mobile/` - Three-tab bottom navigation (Face, Matrix, Feed) — NativeTabs with liquid glass on iOS 26+ - Dark wizard theme (#0A0A12 background, #7C3AED accent) ## WebSocket context (context/TimmyContext.tsx) - Full WebSocket connection to /api/ws with exponential backoff reconnect (1s→30s cap) - Sends visitor_enter handshake on connect, handles ping/pong - Derives timmyMood from agent_state events (idle/thinking/working/speaking) - recentEvents list capped at 100 - sendVisitorMessage() sets mood to "thinking" immediately on send (deterministic waiting state) - speaking mood auto-reverts after estimated TTS duration ## Face tab (app/(tabs)/index.tsx) - Animated 2D wizard face via react-native-svg (hat, head, beard, eyes, pupils, mouth arc, magic orb) - AnimatedPupils: pupilScaleAnim drives actual rendered pupil Circle radius (BASE_PUPIL_R * scale) - AnimatedEyelids: eyeScaleYAnim drives top eyelid overlay via Animated.Value listener - AnimatedMouth: smileAnim + mouthOscAnim combined; SVG Path rebuilt on each frame via listener - speaking mood: 1Hz mouth oscillation via Animated.loop; per-mood body bob speed/amplitude - @react-native-voice/voice installed and statically imported; Voice.onSpeechResults wired properly - startMicPulse/stopMicPulse declared before native voice useEffect (correct hook order) - Web Speech API typed with SpeechRecognitionWindow local interface (zero `any` casts) - sendVisitorMessage() called on final transcript (also triggers thinking mood immediately) - expo-speech TTS speaks Timmy's chat replies on native ## Matrix tab (app/(tabs)/matrix.tsx) - URL normalization: strips existing protocol, uses http for localhost, https for all other hosts - Full-screen WebView with loading spinner and error/retry state; iframe fallback for web ## Feed tab (app/(tabs)/feed.tsx) - FlatList<WsEvent> with proper generics; EventConfig discriminated union (Feather|MaterialCommunityIcons) - Icon names typed via React.ComponentProps["name"] (no `any`) - Color-coded events; event count in header; empty state with connection-aware message ## Type safety - TypeScript typecheck passes with 0 errors - No `any` casts anywhere in new code ## Deviations - expo-av removed (not used; voice input handled via @react-native-voice/voice + Web Speech API) - expo-speech/expo-av NOT in app.json plugins (no config plugins — causes PluginError if listed) - app.json extra.apiDomain added for env-driven domain configuration per requirement - expo-speech pinned ~14.0.8, react-native-webview 13.15.0 for Expo SDK 54 compat - artifact.toml ensurePreviewReachable removed (Expo uses expo-domain router) - @react-native-voice/voice works in Expo Go Android; iOS needs native build (graceful fallback) Replit-Task-Id: 0748cbbf-7b84-4149-8fc0-9d697287a0e6
2026-03-19 23:55:16 +00:00
<Label>Matrix</Label>
</NativeTabs.Trigger>
<NativeTabs.Trigger name=\"feed\">
<Icon sf={{ default: \"list.bullet\", selected: \"list.bullet.circle.fill\" }} />
feat(mobile): scaffold Expo mobile app for Timmy with Face/Matrix/Feed tabs Task #42 — Timmy Harness: Expo Mobile App ## What was built - New Expo artifact at artifacts/mobile, slug `mobile`, preview path `/mobile/` - Three-tab bottom navigation (Face, Matrix, Feed) — NativeTabs with liquid glass on iOS 26+ - Dark wizard theme (#0A0A12 background, #7C3AED accent) ## WebSocket context (context/TimmyContext.tsx) - Full WebSocket connection to /api/ws with exponential backoff reconnect (1s→30s cap) - Sends visitor_enter handshake on connect, handles ping/pong - Derives timmyMood from agent_state events (idle/thinking/working/speaking) - recentEvents list capped at 100 - sendVisitorMessage() sets mood to "thinking" immediately on send (deterministic waiting state) - speaking mood auto-reverts after estimated TTS duration ## Face tab (app/(tabs)/index.tsx) - Animated 2D wizard face via react-native-svg (hat, head, beard, eyes, pupils, mouth arc, magic orb) - AnimatedPupils: pupilScaleAnim drives actual rendered pupil Circle radius (BASE_PUPIL_R * scale) - AnimatedEyelids: eyeScaleYAnim drives top eyelid overlay via Animated.Value listener - AnimatedMouth: smileAnim + mouthOscAnim combined; SVG Path rebuilt on each frame via listener - speaking mood: 1Hz mouth oscillation via Animated.loop; per-mood body bob speed/amplitude - @react-native-voice/voice installed and statically imported; Voice.onSpeechResults wired properly - startMicPulse/stopMicPulse declared before native voice useEffect (correct hook order) - Web Speech API typed with SpeechRecognitionWindow local interface (zero `any` casts) - sendVisitorMessage() called on final transcript (also triggers thinking mood immediately) - expo-speech TTS speaks Timmy's chat replies on native ## Matrix tab (app/(tabs)/matrix.tsx) - URL normalization: strips existing protocol, uses http for localhost, https for all other hosts - Full-screen WebView with loading spinner and error/retry state; iframe fallback for web ## Feed tab (app/(tabs)/feed.tsx) - FlatList<WsEvent> with proper generics; EventConfig discriminated union (Feather|MaterialCommunityIcons) - Icon names typed via React.ComponentProps["name"] (no `any`) - Color-coded events; event count in header; empty state with connection-aware message ## Type safety - TypeScript typecheck passes with 0 errors - No `any` casts anywhere in new code ## Deviations - expo-av removed (not used; voice input handled via @react-native-voice/voice + Web Speech API) - expo-speech/expo-av NOT in app.json plugins (no config plugins — causes PluginError if listed) - app.json extra.apiDomain added for env-driven domain configuration per requirement - expo-speech pinned ~14.0.8, react-native-webview 13.15.0 for Expo SDK 54 compat - artifact.toml ensurePreviewReachable removed (Expo uses expo-domain router) - @react-native-voice/voice works in Expo Go Android; iOS needs native build (graceful fallback) Replit-Task-Id: 0748cbbf-7b84-4149-8fc0-9d697287a0e6
2026-03-19 23:55:16 +00:00
<Label>Feed</Label>
</NativeTabs.Trigger>
</NativeTabs>
);
}
function ClassicTabLayout() {
const insets = useSafeAreaInsets();
const isIOS = Platform.OS === "ios";
const isWeb = Platform.OS === "web";
const C = Colors.dark;
return (
<Tabs
screenOptions={{
headerShown: false,
tabBarActiveTintColor: C.accentGlow,\n tabBarInactiveTintColor: C.textMuted,
feat(mobile): scaffold Expo mobile app for Timmy with Face/Matrix/Feed tabs Task #42 — Timmy Harness: Expo Mobile App ## What was built - New Expo artifact at artifacts/mobile, slug `mobile`, preview path `/mobile/` - Three-tab bottom navigation (Face, Matrix, Feed) — NativeTabs with liquid glass on iOS 26+ - Dark wizard theme (#0A0A12 background, #7C3AED accent) ## WebSocket context (context/TimmyContext.tsx) - Full WebSocket connection to /api/ws with exponential backoff reconnect (1s→30s cap) - Sends visitor_enter handshake on connect, handles ping/pong - Derives timmyMood from agent_state events (idle/thinking/working/speaking) - recentEvents list capped at 100 - sendVisitorMessage() sets mood to "thinking" immediately on send (deterministic waiting state) - speaking mood auto-reverts after estimated TTS duration ## Face tab (app/(tabs)/index.tsx) - Animated 2D wizard face via react-native-svg (hat, head, beard, eyes, pupils, mouth arc, magic orb) - AnimatedPupils: pupilScaleAnim drives actual rendered pupil Circle radius (BASE_PUPIL_R * scale) - AnimatedEyelids: eyeScaleYAnim drives top eyelid overlay via Animated.Value listener - AnimatedMouth: smileAnim + mouthOscAnim combined; SVG Path rebuilt on each frame via listener - speaking mood: 1Hz mouth oscillation via Animated.loop; per-mood body bob speed/amplitude - @react-native-voice/voice installed and statically imported; Voice.onSpeechResults wired properly - startMicPulse/stopMicPulse declared before native voice useEffect (correct hook order) - Web Speech API typed with SpeechRecognitionWindow local interface (zero `any` casts) - sendVisitorMessage() called on final transcript (also triggers thinking mood immediately) - expo-speech TTS speaks Timmy's chat replies on native ## Matrix tab (app/(tabs)/matrix.tsx) - URL normalization: strips existing protocol, uses http for localhost, https for all other hosts - Full-screen WebView with loading spinner and error/retry state; iframe fallback for web ## Feed tab (app/(tabs)/feed.tsx) - FlatList<WsEvent> with proper generics; EventConfig discriminated union (Feather|MaterialCommunityIcons) - Icon names typed via React.ComponentProps["name"] (no `any`) - Color-coded events; event count in header; empty state with connection-aware message ## Type safety - TypeScript typecheck passes with 0 errors - No `any` casts anywhere in new code ## Deviations - expo-av removed (not used; voice input handled via @react-native-voice/voice + Web Speech API) - expo-speech/expo-av NOT in app.json plugins (no config plugins — causes PluginError if listed) - app.json extra.apiDomain added for env-driven domain configuration per requirement - expo-speech pinned ~14.0.8, react-native-webview 13.15.0 for Expo SDK 54 compat - artifact.toml ensurePreviewReachable removed (Expo uses expo-domain router) - @react-native-voice/voice works in Expo Go Android; iOS needs native build (graceful fallback) Replit-Task-Id: 0748cbbf-7b84-4149-8fc0-9d697287a0e6
2026-03-19 23:55:16 +00:00
tabBarStyle: {
position: "absolute",
backgroundColor: isIOS ? "transparent" : C.surface,
borderTopWidth: 0,
elevation: 0,
...(isWeb ? { height: 84 } : {}),
},
tabBarBackground: () =>
isIOS ? (
<BlurView
intensity={80}
tint=\"dark\"
feat(mobile): scaffold Expo mobile app for Timmy with Face/Matrix/Feed tabs Task #42 — Timmy Harness: Expo Mobile App ## What was built - New Expo artifact at artifacts/mobile, slug `mobile`, preview path `/mobile/` - Three-tab bottom navigation (Face, Matrix, Feed) — NativeTabs with liquid glass on iOS 26+ - Dark wizard theme (#0A0A12 background, #7C3AED accent) ## WebSocket context (context/TimmyContext.tsx) - Full WebSocket connection to /api/ws with exponential backoff reconnect (1s→30s cap) - Sends visitor_enter handshake on connect, handles ping/pong - Derives timmyMood from agent_state events (idle/thinking/working/speaking) - recentEvents list capped at 100 - sendVisitorMessage() sets mood to "thinking" immediately on send (deterministic waiting state) - speaking mood auto-reverts after estimated TTS duration ## Face tab (app/(tabs)/index.tsx) - Animated 2D wizard face via react-native-svg (hat, head, beard, eyes, pupils, mouth arc, magic orb) - AnimatedPupils: pupilScaleAnim drives actual rendered pupil Circle radius (BASE_PUPIL_R * scale) - AnimatedEyelids: eyeScaleYAnim drives top eyelid overlay via Animated.Value listener - AnimatedMouth: smileAnim + mouthOscAnim combined; SVG Path rebuilt on each frame via listener - speaking mood: 1Hz mouth oscillation via Animated.loop; per-mood body bob speed/amplitude - @react-native-voice/voice installed and statically imported; Voice.onSpeechResults wired properly - startMicPulse/stopMicPulse declared before native voice useEffect (correct hook order) - Web Speech API typed with SpeechRecognitionWindow local interface (zero `any` casts) - sendVisitorMessage() called on final transcript (also triggers thinking mood immediately) - expo-speech TTS speaks Timmy's chat replies on native ## Matrix tab (app/(tabs)/matrix.tsx) - URL normalization: strips existing protocol, uses http for localhost, https for all other hosts - Full-screen WebView with loading spinner and error/retry state; iframe fallback for web ## Feed tab (app/(tabs)/feed.tsx) - FlatList<WsEvent> with proper generics; EventConfig discriminated union (Feather|MaterialCommunityIcons) - Icon names typed via React.ComponentProps["name"] (no `any`) - Color-coded events; event count in header; empty state with connection-aware message ## Type safety - TypeScript typecheck passes with 0 errors - No `any` casts anywhere in new code ## Deviations - expo-av removed (not used; voice input handled via @react-native-voice/voice + Web Speech API) - expo-speech/expo-av NOT in app.json plugins (no config plugins — causes PluginError if listed) - app.json extra.apiDomain added for env-driven domain configuration per requirement - expo-speech pinned ~14.0.8, react-native-webview 13.15.0 for Expo SDK 54 compat - artifact.toml ensurePreviewReachable removed (Expo uses expo-domain router) - @react-native-voice/voice works in Expo Go Android; iOS needs native build (graceful fallback) Replit-Task-Id: 0748cbbf-7b84-4149-8fc0-9d697287a0e6
2026-03-19 23:55:16 +00:00
style={[StyleSheet.absoluteFill, { borderTopWidth: 0.5, borderTopColor: C.border }]}
/>
) : isWeb ? (
<View
style={[StyleSheet.absoluteFill, { backgroundColor: C.surface, borderTopWidth: 0.5, borderTopColor: C.border }]}
/>
) : (
<View style={[StyleSheet.absoluteFill, { backgroundColor: C.surface, borderTopWidth: 0.5, borderTopColor: C.border }]} />
),\
}}\
feat(mobile): scaffold Expo mobile app for Timmy with Face/Matrix/Feed tabs Task #42 — Timmy Harness: Expo Mobile App ## What was built - New Expo artifact at artifacts/mobile, slug `mobile`, preview path `/mobile/` - Three-tab bottom navigation (Face, Matrix, Feed) — NativeTabs with liquid glass on iOS 26+ - Dark wizard theme (#0A0A12 background, #7C3AED accent) ## WebSocket context (context/TimmyContext.tsx) - Full WebSocket connection to /api/ws with exponential backoff reconnect (1s→30s cap) - Sends visitor_enter handshake on connect, handles ping/pong - Derives timmyMood from agent_state events (idle/thinking/working/speaking) - recentEvents list capped at 100 - sendVisitorMessage() sets mood to "thinking" immediately on send (deterministic waiting state) - speaking mood auto-reverts after estimated TTS duration ## Face tab (app/(tabs)/index.tsx) - Animated 2D wizard face via react-native-svg (hat, head, beard, eyes, pupils, mouth arc, magic orb) - AnimatedPupils: pupilScaleAnim drives actual rendered pupil Circle radius (BASE_PUPIL_R * scale) - AnimatedEyelids: eyeScaleYAnim drives top eyelid overlay via Animated.Value listener - AnimatedMouth: smileAnim + mouthOscAnim combined; SVG Path rebuilt on each frame via listener - speaking mood: 1Hz mouth oscillation via Animated.loop; per-mood body bob speed/amplitude - @react-native-voice/voice installed and statically imported; Voice.onSpeechResults wired properly - startMicPulse/stopMicPulse declared before native voice useEffect (correct hook order) - Web Speech API typed with SpeechRecognitionWindow local interface (zero `any` casts) - sendVisitorMessage() called on final transcript (also triggers thinking mood immediately) - expo-speech TTS speaks Timmy's chat replies on native ## Matrix tab (app/(tabs)/matrix.tsx) - URL normalization: strips existing protocol, uses http for localhost, https for all other hosts - Full-screen WebView with loading spinner and error/retry state; iframe fallback for web ## Feed tab (app/(tabs)/feed.tsx) - FlatList<WsEvent> with proper generics; EventConfig discriminated union (Feather|MaterialCommunityIcons) - Icon names typed via React.ComponentProps["name"] (no `any`) - Color-coded events; event count in header; empty state with connection-aware message ## Type safety - TypeScript typecheck passes with 0 errors - No `any` casts anywhere in new code ## Deviations - expo-av removed (not used; voice input handled via @react-native-voice/voice + Web Speech API) - expo-speech/expo-av NOT in app.json plugins (no config plugins — causes PluginError if listed) - app.json extra.apiDomain added for env-driven domain configuration per requirement - expo-speech pinned ~14.0.8, react-native-webview 13.15.0 for Expo SDK 54 compat - artifact.toml ensurePreviewReachable removed (Expo uses expo-domain router) - @react-native-voice/voice works in Expo Go Android; iOS needs native build (graceful fallback) Replit-Task-Id: 0748cbbf-7b84-4149-8fc0-9d697287a0e6
2026-03-19 23:55:16 +00:00
>
<Tabs.Screen
name=\"index\"
feat(mobile): scaffold Expo mobile app for Timmy with Face/Matrix/Feed tabs Task #42 — Timmy Harness: Expo Mobile App ## What was built - New Expo artifact at artifacts/mobile, slug `mobile`, preview path `/mobile/` - Three-tab bottom navigation (Face, Matrix, Feed) — NativeTabs with liquid glass on iOS 26+ - Dark wizard theme (#0A0A12 background, #7C3AED accent) ## WebSocket context (context/TimmyContext.tsx) - Full WebSocket connection to /api/ws with exponential backoff reconnect (1s→30s cap) - Sends visitor_enter handshake on connect, handles ping/pong - Derives timmyMood from agent_state events (idle/thinking/working/speaking) - recentEvents list capped at 100 - sendVisitorMessage() sets mood to "thinking" immediately on send (deterministic waiting state) - speaking mood auto-reverts after estimated TTS duration ## Face tab (app/(tabs)/index.tsx) - Animated 2D wizard face via react-native-svg (hat, head, beard, eyes, pupils, mouth arc, magic orb) - AnimatedPupils: pupilScaleAnim drives actual rendered pupil Circle radius (BASE_PUPIL_R * scale) - AnimatedEyelids: eyeScaleYAnim drives top eyelid overlay via Animated.Value listener - AnimatedMouth: smileAnim + mouthOscAnim combined; SVG Path rebuilt on each frame via listener - speaking mood: 1Hz mouth oscillation via Animated.loop; per-mood body bob speed/amplitude - @react-native-voice/voice installed and statically imported; Voice.onSpeechResults wired properly - startMicPulse/stopMicPulse declared before native voice useEffect (correct hook order) - Web Speech API typed with SpeechRecognitionWindow local interface (zero `any` casts) - sendVisitorMessage() called on final transcript (also triggers thinking mood immediately) - expo-speech TTS speaks Timmy's chat replies on native ## Matrix tab (app/(tabs)/matrix.tsx) - URL normalization: strips existing protocol, uses http for localhost, https for all other hosts - Full-screen WebView with loading spinner and error/retry state; iframe fallback for web ## Feed tab (app/(tabs)/feed.tsx) - FlatList<WsEvent> with proper generics; EventConfig discriminated union (Feather|MaterialCommunityIcons) - Icon names typed via React.ComponentProps["name"] (no `any`) - Color-coded events; event count in header; empty state with connection-aware message ## Type safety - TypeScript typecheck passes with 0 errors - No `any` casts anywhere in new code ## Deviations - expo-av removed (not used; voice input handled via @react-native-voice/voice + Web Speech API) - expo-speech/expo-av NOT in app.json plugins (no config plugins — causes PluginError if listed) - app.json extra.apiDomain added for env-driven domain configuration per requirement - expo-speech pinned ~14.0.8, react-native-webview 13.15.0 for Expo SDK 54 compat - artifact.toml ensurePreviewReachable removed (Expo uses expo-domain router) - @react-native-voice/voice works in Expo Go Android; iOS needs native build (graceful fallback) Replit-Task-Id: 0748cbbf-7b84-4149-8fc0-9d697287a0e6
2026-03-19 23:55:16 +00:00
options={{
title: "Timmy",
headerShown: true,
headerRight: () => (\n <Link href=\"/settings\" asChild>\n <Pressable style={({ pressed }) => ({ opacity: pressed ? 0.5 : 1 })}>
<Ionicons name=\"settings-outline\" size={24} color={C.text} style={{ marginRight: 15 }} />\n </Pressable>\n </Link>\n ),
feat(mobile): scaffold Expo mobile app for Timmy with Face/Matrix/Feed tabs Task #42 — Timmy Harness: Expo Mobile App ## What was built - New Expo artifact at artifacts/mobile, slug `mobile`, preview path `/mobile/` - Three-tab bottom navigation (Face, Matrix, Feed) — NativeTabs with liquid glass on iOS 26+ - Dark wizard theme (#0A0A12 background, #7C3AED accent) ## WebSocket context (context/TimmyContext.tsx) - Full WebSocket connection to /api/ws with exponential backoff reconnect (1s→30s cap) - Sends visitor_enter handshake on connect, handles ping/pong - Derives timmyMood from agent_state events (idle/thinking/working/speaking) - recentEvents list capped at 100 - sendVisitorMessage() sets mood to "thinking" immediately on send (deterministic waiting state) - speaking mood auto-reverts after estimated TTS duration ## Face tab (app/(tabs)/index.tsx) - Animated 2D wizard face via react-native-svg (hat, head, beard, eyes, pupils, mouth arc, magic orb) - AnimatedPupils: pupilScaleAnim drives actual rendered pupil Circle radius (BASE_PUPIL_R * scale) - AnimatedEyelids: eyeScaleYAnim drives top eyelid overlay via Animated.Value listener - AnimatedMouth: smileAnim + mouthOscAnim combined; SVG Path rebuilt on each frame via listener - speaking mood: 1Hz mouth oscillation via Animated.loop; per-mood body bob speed/amplitude - @react-native-voice/voice installed and statically imported; Voice.onSpeechResults wired properly - startMicPulse/stopMicPulse declared before native voice useEffect (correct hook order) - Web Speech API typed with SpeechRecognitionWindow local interface (zero `any` casts) - sendVisitorMessage() called on final transcript (also triggers thinking mood immediately) - expo-speech TTS speaks Timmy's chat replies on native ## Matrix tab (app/(tabs)/matrix.tsx) - URL normalization: strips existing protocol, uses http for localhost, https for all other hosts - Full-screen WebView with loading spinner and error/retry state; iframe fallback for web ## Feed tab (app/(tabs)/feed.tsx) - FlatList<WsEvent> with proper generics; EventConfig discriminated union (Feather|MaterialCommunityIcons) - Icon names typed via React.ComponentProps["name"] (no `any`) - Color-coded events; event count in header; empty state with connection-aware message ## Type safety - TypeScript typecheck passes with 0 errors - No `any` casts anywhere in new code ## Deviations - expo-av removed (not used; voice input handled via @react-native-voice/voice + Web Speech API) - expo-speech/expo-av NOT in app.json plugins (no config plugins — causes PluginError if listed) - app.json extra.apiDomain added for env-driven domain configuration per requirement - expo-speech pinned ~14.0.8, react-native-webview 13.15.0 for Expo SDK 54 compat - artifact.toml ensurePreviewReachable removed (Expo uses expo-domain router) - @react-native-voice/voice works in Expo Go Android; iOS needs native build (graceful fallback) Replit-Task-Id: 0748cbbf-7b84-4149-8fc0-9d697287a0e6
2026-03-19 23:55:16 +00:00
tabBarIcon: ({ color, size }) =>
isIOS ? (
<SymbolView name=\"face.smiling\" tintColor={color} size={size} />
feat(mobile): scaffold Expo mobile app for Timmy with Face/Matrix/Feed tabs Task #42 — Timmy Harness: Expo Mobile App ## What was built - New Expo artifact at artifacts/mobile, slug `mobile`, preview path `/mobile/` - Three-tab bottom navigation (Face, Matrix, Feed) — NativeTabs with liquid glass on iOS 26+ - Dark wizard theme (#0A0A12 background, #7C3AED accent) ## WebSocket context (context/TimmyContext.tsx) - Full WebSocket connection to /api/ws with exponential backoff reconnect (1s→30s cap) - Sends visitor_enter handshake on connect, handles ping/pong - Derives timmyMood from agent_state events (idle/thinking/working/speaking) - recentEvents list capped at 100 - sendVisitorMessage() sets mood to "thinking" immediately on send (deterministic waiting state) - speaking mood auto-reverts after estimated TTS duration ## Face tab (app/(tabs)/index.tsx) - Animated 2D wizard face via react-native-svg (hat, head, beard, eyes, pupils, mouth arc, magic orb) - AnimatedPupils: pupilScaleAnim drives actual rendered pupil Circle radius (BASE_PUPIL_R * scale) - AnimatedEyelids: eyeScaleYAnim drives top eyelid overlay via Animated.Value listener - AnimatedMouth: smileAnim + mouthOscAnim combined; SVG Path rebuilt on each frame via listener - speaking mood: 1Hz mouth oscillation via Animated.loop; per-mood body bob speed/amplitude - @react-native-voice/voice installed and statically imported; Voice.onSpeechResults wired properly - startMicPulse/stopMicPulse declared before native voice useEffect (correct hook order) - Web Speech API typed with SpeechRecognitionWindow local interface (zero `any` casts) - sendVisitorMessage() called on final transcript (also triggers thinking mood immediately) - expo-speech TTS speaks Timmy's chat replies on native ## Matrix tab (app/(tabs)/matrix.tsx) - URL normalization: strips existing protocol, uses http for localhost, https for all other hosts - Full-screen WebView with loading spinner and error/retry state; iframe fallback for web ## Feed tab (app/(tabs)/feed.tsx) - FlatList<WsEvent> with proper generics; EventConfig discriminated union (Feather|MaterialCommunityIcons) - Icon names typed via React.ComponentProps["name"] (no `any`) - Color-coded events; event count in header; empty state with connection-aware message ## Type safety - TypeScript typecheck passes with 0 errors - No `any` casts anywhere in new code ## Deviations - expo-av removed (not used; voice input handled via @react-native-voice/voice + Web Speech API) - expo-speech/expo-av NOT in app.json plugins (no config plugins — causes PluginError if listed) - app.json extra.apiDomain added for env-driven domain configuration per requirement - expo-speech pinned ~14.0.8, react-native-webview 13.15.0 for Expo SDK 54 compat - artifact.toml ensurePreviewReachable removed (Expo uses expo-domain router) - @react-native-voice/voice works in Expo Go Android; iOS needs native build (graceful fallback) Replit-Task-Id: 0748cbbf-7b84-4149-8fc0-9d697287a0e6
2026-03-19 23:55:16 +00:00
) : (
<MaterialCommunityIcons name=\"emoticon-outline\" size={size} color={color} />
),\
}}\
feat(mobile): scaffold Expo mobile app for Timmy with Face/Matrix/Feed tabs Task #42 — Timmy Harness: Expo Mobile App ## What was built - New Expo artifact at artifacts/mobile, slug `mobile`, preview path `/mobile/` - Three-tab bottom navigation (Face, Matrix, Feed) — NativeTabs with liquid glass on iOS 26+ - Dark wizard theme (#0A0A12 background, #7C3AED accent) ## WebSocket context (context/TimmyContext.tsx) - Full WebSocket connection to /api/ws with exponential backoff reconnect (1s→30s cap) - Sends visitor_enter handshake on connect, handles ping/pong - Derives timmyMood from agent_state events (idle/thinking/working/speaking) - recentEvents list capped at 100 - sendVisitorMessage() sets mood to "thinking" immediately on send (deterministic waiting state) - speaking mood auto-reverts after estimated TTS duration ## Face tab (app/(tabs)/index.tsx) - Animated 2D wizard face via react-native-svg (hat, head, beard, eyes, pupils, mouth arc, magic orb) - AnimatedPupils: pupilScaleAnim drives actual rendered pupil Circle radius (BASE_PUPIL_R * scale) - AnimatedEyelids: eyeScaleYAnim drives top eyelid overlay via Animated.Value listener - AnimatedMouth: smileAnim + mouthOscAnim combined; SVG Path rebuilt on each frame via listener - speaking mood: 1Hz mouth oscillation via Animated.loop; per-mood body bob speed/amplitude - @react-native-voice/voice installed and statically imported; Voice.onSpeechResults wired properly - startMicPulse/stopMicPulse declared before native voice useEffect (correct hook order) - Web Speech API typed with SpeechRecognitionWindow local interface (zero `any` casts) - sendVisitorMessage() called on final transcript (also triggers thinking mood immediately) - expo-speech TTS speaks Timmy's chat replies on native ## Matrix tab (app/(tabs)/matrix.tsx) - URL normalization: strips existing protocol, uses http for localhost, https for all other hosts - Full-screen WebView with loading spinner and error/retry state; iframe fallback for web ## Feed tab (app/(tabs)/feed.tsx) - FlatList<WsEvent> with proper generics; EventConfig discriminated union (Feather|MaterialCommunityIcons) - Icon names typed via React.ComponentProps["name"] (no `any`) - Color-coded events; event count in header; empty state with connection-aware message ## Type safety - TypeScript typecheck passes with 0 errors - No `any` casts anywhere in new code ## Deviations - expo-av removed (not used; voice input handled via @react-native-voice/voice + Web Speech API) - expo-speech/expo-av NOT in app.json plugins (no config plugins — causes PluginError if listed) - app.json extra.apiDomain added for env-driven domain configuration per requirement - expo-speech pinned ~14.0.8, react-native-webview 13.15.0 for Expo SDK 54 compat - artifact.toml ensurePreviewReachable removed (Expo uses expo-domain router) - @react-native-voice/voice works in Expo Go Android; iOS needs native build (graceful fallback) Replit-Task-Id: 0748cbbf-7b84-4149-8fc0-9d697287a0e6
2026-03-19 23:55:16 +00:00
/>
<Tabs.Screen
name=\"matrix\"
feat(mobile): scaffold Expo mobile app for Timmy with Face/Matrix/Feed tabs Task #42 — Timmy Harness: Expo Mobile App ## What was built - New Expo artifact at artifacts/mobile, slug `mobile`, preview path `/mobile/` - Three-tab bottom navigation (Face, Matrix, Feed) — NativeTabs with liquid glass on iOS 26+ - Dark wizard theme (#0A0A12 background, #7C3AED accent) ## WebSocket context (context/TimmyContext.tsx) - Full WebSocket connection to /api/ws with exponential backoff reconnect (1s→30s cap) - Sends visitor_enter handshake on connect, handles ping/pong - Derives timmyMood from agent_state events (idle/thinking/working/speaking) - recentEvents list capped at 100 - sendVisitorMessage() sets mood to "thinking" immediately on send (deterministic waiting state) - speaking mood auto-reverts after estimated TTS duration ## Face tab (app/(tabs)/index.tsx) - Animated 2D wizard face via react-native-svg (hat, head, beard, eyes, pupils, mouth arc, magic orb) - AnimatedPupils: pupilScaleAnim drives actual rendered pupil Circle radius (BASE_PUPIL_R * scale) - AnimatedEyelids: eyeScaleYAnim drives top eyelid overlay via Animated.Value listener - AnimatedMouth: smileAnim + mouthOscAnim combined; SVG Path rebuilt on each frame via listener - speaking mood: 1Hz mouth oscillation via Animated.loop; per-mood body bob speed/amplitude - @react-native-voice/voice installed and statically imported; Voice.onSpeechResults wired properly - startMicPulse/stopMicPulse declared before native voice useEffect (correct hook order) - Web Speech API typed with SpeechRecognitionWindow local interface (zero `any` casts) - sendVisitorMessage() called on final transcript (also triggers thinking mood immediately) - expo-speech TTS speaks Timmy's chat replies on native ## Matrix tab (app/(tabs)/matrix.tsx) - URL normalization: strips existing protocol, uses http for localhost, https for all other hosts - Full-screen WebView with loading spinner and error/retry state; iframe fallback for web ## Feed tab (app/(tabs)/feed.tsx) - FlatList<WsEvent> with proper generics; EventConfig discriminated union (Feather|MaterialCommunityIcons) - Icon names typed via React.ComponentProps["name"] (no `any`) - Color-coded events; event count in header; empty state with connection-aware message ## Type safety - TypeScript typecheck passes with 0 errors - No `any` casts anywhere in new code ## Deviations - expo-av removed (not used; voice input handled via @react-native-voice/voice + Web Speech API) - expo-speech/expo-av NOT in app.json plugins (no config plugins — causes PluginError if listed) - app.json extra.apiDomain added for env-driven domain configuration per requirement - expo-speech pinned ~14.0.8, react-native-webview 13.15.0 for Expo SDK 54 compat - artifact.toml ensurePreviewReachable removed (Expo uses expo-domain router) - @react-native-voice/voice works in Expo Go Android; iOS needs native build (graceful fallback) Replit-Task-Id: 0748cbbf-7b84-4149-8fc0-9d697287a0e6
2026-03-19 23:55:16 +00:00
options={{
title: "Matrix",
tabBarIcon: ({ color, size }) =>
isIOS ? (
<SymbolView name=\"cube\" tintColor={color} size={size} />
feat(mobile): scaffold Expo mobile app for Timmy with Face/Matrix/Feed tabs Task #42 — Timmy Harness: Expo Mobile App ## What was built - New Expo artifact at artifacts/mobile, slug `mobile`, preview path `/mobile/` - Three-tab bottom navigation (Face, Matrix, Feed) — NativeTabs with liquid glass on iOS 26+ - Dark wizard theme (#0A0A12 background, #7C3AED accent) ## WebSocket context (context/TimmyContext.tsx) - Full WebSocket connection to /api/ws with exponential backoff reconnect (1s→30s cap) - Sends visitor_enter handshake on connect, handles ping/pong - Derives timmyMood from agent_state events (idle/thinking/working/speaking) - recentEvents list capped at 100 - sendVisitorMessage() sets mood to "thinking" immediately on send (deterministic waiting state) - speaking mood auto-reverts after estimated TTS duration ## Face tab (app/(tabs)/index.tsx) - Animated 2D wizard face via react-native-svg (hat, head, beard, eyes, pupils, mouth arc, magic orb) - AnimatedPupils: pupilScaleAnim drives actual rendered pupil Circle radius (BASE_PUPIL_R * scale) - AnimatedEyelids: eyeScaleYAnim drives top eyelid overlay via Animated.Value listener - AnimatedMouth: smileAnim + mouthOscAnim combined; SVG Path rebuilt on each frame via listener - speaking mood: 1Hz mouth oscillation via Animated.loop; per-mood body bob speed/amplitude - @react-native-voice/voice installed and statically imported; Voice.onSpeechResults wired properly - startMicPulse/stopMicPulse declared before native voice useEffect (correct hook order) - Web Speech API typed with SpeechRecognitionWindow local interface (zero `any` casts) - sendVisitorMessage() called on final transcript (also triggers thinking mood immediately) - expo-speech TTS speaks Timmy's chat replies on native ## Matrix tab (app/(tabs)/matrix.tsx) - URL normalization: strips existing protocol, uses http for localhost, https for all other hosts - Full-screen WebView with loading spinner and error/retry state; iframe fallback for web ## Feed tab (app/(tabs)/feed.tsx) - FlatList<WsEvent> with proper generics; EventConfig discriminated union (Feather|MaterialCommunityIcons) - Icon names typed via React.ComponentProps["name"] (no `any`) - Color-coded events; event count in header; empty state with connection-aware message ## Type safety - TypeScript typecheck passes with 0 errors - No `any` casts anywhere in new code ## Deviations - expo-av removed (not used; voice input handled via @react-native-voice/voice + Web Speech API) - expo-speech/expo-av NOT in app.json plugins (no config plugins — causes PluginError if listed) - app.json extra.apiDomain added for env-driven domain configuration per requirement - expo-speech pinned ~14.0.8, react-native-webview 13.15.0 for Expo SDK 54 compat - artifact.toml ensurePreviewReachable removed (Expo uses expo-domain router) - @react-native-voice/voice works in Expo Go Android; iOS needs native build (graceful fallback) Replit-Task-Id: 0748cbbf-7b84-4149-8fc0-9d697287a0e6
2026-03-19 23:55:16 +00:00
) : (
<MaterialCommunityIcons name=\"cube-outline\" size={size} color={color} />
),\
}}\
feat(mobile): scaffold Expo mobile app for Timmy with Face/Matrix/Feed tabs Task #42 — Timmy Harness: Expo Mobile App ## What was built - New Expo artifact at artifacts/mobile, slug `mobile`, preview path `/mobile/` - Three-tab bottom navigation (Face, Matrix, Feed) — NativeTabs with liquid glass on iOS 26+ - Dark wizard theme (#0A0A12 background, #7C3AED accent) ## WebSocket context (context/TimmyContext.tsx) - Full WebSocket connection to /api/ws with exponential backoff reconnect (1s→30s cap) - Sends visitor_enter handshake on connect, handles ping/pong - Derives timmyMood from agent_state events (idle/thinking/working/speaking) - recentEvents list capped at 100 - sendVisitorMessage() sets mood to "thinking" immediately on send (deterministic waiting state) - speaking mood auto-reverts after estimated TTS duration ## Face tab (app/(tabs)/index.tsx) - Animated 2D wizard face via react-native-svg (hat, head, beard, eyes, pupils, mouth arc, magic orb) - AnimatedPupils: pupilScaleAnim drives actual rendered pupil Circle radius (BASE_PUPIL_R * scale) - AnimatedEyelids: eyeScaleYAnim drives top eyelid overlay via Animated.Value listener - AnimatedMouth: smileAnim + mouthOscAnim combined; SVG Path rebuilt on each frame via listener - speaking mood: 1Hz mouth oscillation via Animated.loop; per-mood body bob speed/amplitude - @react-native-voice/voice installed and statically imported; Voice.onSpeechResults wired properly - startMicPulse/stopMicPulse declared before native voice useEffect (correct hook order) - Web Speech API typed with SpeechRecognitionWindow local interface (zero `any` casts) - sendVisitorMessage() called on final transcript (also triggers thinking mood immediately) - expo-speech TTS speaks Timmy's chat replies on native ## Matrix tab (app/(tabs)/matrix.tsx) - URL normalization: strips existing protocol, uses http for localhost, https for all other hosts - Full-screen WebView with loading spinner and error/retry state; iframe fallback for web ## Feed tab (app/(tabs)/feed.tsx) - FlatList<WsEvent> with proper generics; EventConfig discriminated union (Feather|MaterialCommunityIcons) - Icon names typed via React.ComponentProps["name"] (no `any`) - Color-coded events; event count in header; empty state with connection-aware message ## Type safety - TypeScript typecheck passes with 0 errors - No `any` casts anywhere in new code ## Deviations - expo-av removed (not used; voice input handled via @react-native-voice/voice + Web Speech API) - expo-speech/expo-av NOT in app.json plugins (no config plugins — causes PluginError if listed) - app.json extra.apiDomain added for env-driven domain configuration per requirement - expo-speech pinned ~14.0.8, react-native-webview 13.15.0 for Expo SDK 54 compat - artifact.toml ensurePreviewReachable removed (Expo uses expo-domain router) - @react-native-voice/voice works in Expo Go Android; iOS needs native build (graceful fallback) Replit-Task-Id: 0748cbbf-7b84-4149-8fc0-9d697287a0e6
2026-03-19 23:55:16 +00:00
/>
<Tabs.Screen
name=\"feed\"
feat(mobile): scaffold Expo mobile app for Timmy with Face/Matrix/Feed tabs Task #42 — Timmy Harness: Expo Mobile App ## What was built - New Expo artifact at artifacts/mobile, slug `mobile`, preview path `/mobile/` - Three-tab bottom navigation (Face, Matrix, Feed) — NativeTabs with liquid glass on iOS 26+ - Dark wizard theme (#0A0A12 background, #7C3AED accent) ## WebSocket context (context/TimmyContext.tsx) - Full WebSocket connection to /api/ws with exponential backoff reconnect (1s→30s cap) - Sends visitor_enter handshake on connect, handles ping/pong - Derives timmyMood from agent_state events (idle/thinking/working/speaking) - recentEvents list capped at 100 - sendVisitorMessage() sets mood to "thinking" immediately on send (deterministic waiting state) - speaking mood auto-reverts after estimated TTS duration ## Face tab (app/(tabs)/index.tsx) - Animated 2D wizard face via react-native-svg (hat, head, beard, eyes, pupils, mouth arc, magic orb) - AnimatedPupils: pupilScaleAnim drives actual rendered pupil Circle radius (BASE_PUPIL_R * scale) - AnimatedEyelids: eyeScaleYAnim drives top eyelid overlay via Animated.Value listener - AnimatedMouth: smileAnim + mouthOscAnim combined; SVG Path rebuilt on each frame via listener - speaking mood: 1Hz mouth oscillation via Animated.loop; per-mood body bob speed/amplitude - @react-native-voice/voice installed and statically imported; Voice.onSpeechResults wired properly - startMicPulse/stopMicPulse declared before native voice useEffect (correct hook order) - Web Speech API typed with SpeechRecognitionWindow local interface (zero `any` casts) - sendVisitorMessage() called on final transcript (also triggers thinking mood immediately) - expo-speech TTS speaks Timmy's chat replies on native ## Matrix tab (app/(tabs)/matrix.tsx) - URL normalization: strips existing protocol, uses http for localhost, https for all other hosts - Full-screen WebView with loading spinner and error/retry state; iframe fallback for web ## Feed tab (app/(tabs)/feed.tsx) - FlatList<WsEvent> with proper generics; EventConfig discriminated union (Feather|MaterialCommunityIcons) - Icon names typed via React.ComponentProps["name"] (no `any`) - Color-coded events; event count in header; empty state with connection-aware message ## Type safety - TypeScript typecheck passes with 0 errors - No `any` casts anywhere in new code ## Deviations - expo-av removed (not used; voice input handled via @react-native-voice/voice + Web Speech API) - expo-speech/expo-av NOT in app.json plugins (no config plugins — causes PluginError if listed) - app.json extra.apiDomain added for env-driven domain configuration per requirement - expo-speech pinned ~14.0.8, react-native-webview 13.15.0 for Expo SDK 54 compat - artifact.toml ensurePreviewReachable removed (Expo uses expo-domain router) - @react-native-voice/voice works in Expo Go Android; iOS needs native build (graceful fallback) Replit-Task-Id: 0748cbbf-7b84-4149-8fc0-9d697287a0e6
2026-03-19 23:55:16 +00:00
options={{
title: "Feed",
tabBarIcon: ({ color, size }) =>
isIOS ? (
<SymbolView name=\"list.bullet\" tintColor={color} size={size} />
feat(mobile): scaffold Expo mobile app for Timmy with Face/Matrix/Feed tabs Task #42 — Timmy Harness: Expo Mobile App ## What was built - New Expo artifact at artifacts/mobile, slug `mobile`, preview path `/mobile/` - Three-tab bottom navigation (Face, Matrix, Feed) — NativeTabs with liquid glass on iOS 26+ - Dark wizard theme (#0A0A12 background, #7C3AED accent) ## WebSocket context (context/TimmyContext.tsx) - Full WebSocket connection to /api/ws with exponential backoff reconnect (1s→30s cap) - Sends visitor_enter handshake on connect, handles ping/pong - Derives timmyMood from agent_state events (idle/thinking/working/speaking) - recentEvents list capped at 100 - sendVisitorMessage() sets mood to "thinking" immediately on send (deterministic waiting state) - speaking mood auto-reverts after estimated TTS duration ## Face tab (app/(tabs)/index.tsx) - Animated 2D wizard face via react-native-svg (hat, head, beard, eyes, pupils, mouth arc, magic orb) - AnimatedPupils: pupilScaleAnim drives actual rendered pupil Circle radius (BASE_PUPIL_R * scale) - AnimatedEyelids: eyeScaleYAnim drives top eyelid overlay via Animated.Value listener - AnimatedMouth: smileAnim + mouthOscAnim combined; SVG Path rebuilt on each frame via listener - speaking mood: 1Hz mouth oscillation via Animated.loop; per-mood body bob speed/amplitude - @react-native-voice/voice installed and statically imported; Voice.onSpeechResults wired properly - startMicPulse/stopMicPulse declared before native voice useEffect (correct hook order) - Web Speech API typed with SpeechRecognitionWindow local interface (zero `any` casts) - sendVisitorMessage() called on final transcript (also triggers thinking mood immediately) - expo-speech TTS speaks Timmy's chat replies on native ## Matrix tab (app/(tabs)/matrix.tsx) - URL normalization: strips existing protocol, uses http for localhost, https for all other hosts - Full-screen WebView with loading spinner and error/retry state; iframe fallback for web ## Feed tab (app/(tabs)/feed.tsx) - FlatList<WsEvent> with proper generics; EventConfig discriminated union (Feather|MaterialCommunityIcons) - Icon names typed via React.ComponentProps["name"] (no `any`) - Color-coded events; event count in header; empty state with connection-aware message ## Type safety - TypeScript typecheck passes with 0 errors - No `any` casts anywhere in new code ## Deviations - expo-av removed (not used; voice input handled via @react-native-voice/voice + Web Speech API) - expo-speech/expo-av NOT in app.json plugins (no config plugins — causes PluginError if listed) - app.json extra.apiDomain added for env-driven domain configuration per requirement - expo-speech pinned ~14.0.8, react-native-webview 13.15.0 for Expo SDK 54 compat - artifact.toml ensurePreviewReachable removed (Expo uses expo-domain router) - @react-native-voice/voice works in Expo Go Android; iOS needs native build (graceful fallback) Replit-Task-Id: 0748cbbf-7b84-4149-8fc0-9d697287a0e6
2026-03-19 23:55:16 +00:00
) : (
<Feather name=\"activity\" size={size} color={color} />
),\
}}\
feat(mobile): scaffold Expo mobile app for Timmy with Face/Matrix/Feed tabs Task #42 — Timmy Harness: Expo Mobile App ## What was built - New Expo artifact at artifacts/mobile, slug `mobile`, preview path `/mobile/` - Three-tab bottom navigation (Face, Matrix, Feed) — NativeTabs with liquid glass on iOS 26+ - Dark wizard theme (#0A0A12 background, #7C3AED accent) ## WebSocket context (context/TimmyContext.tsx) - Full WebSocket connection to /api/ws with exponential backoff reconnect (1s→30s cap) - Sends visitor_enter handshake on connect, handles ping/pong - Derives timmyMood from agent_state events (idle/thinking/working/speaking) - recentEvents list capped at 100 - sendVisitorMessage() sets mood to "thinking" immediately on send (deterministic waiting state) - speaking mood auto-reverts after estimated TTS duration ## Face tab (app/(tabs)/index.tsx) - Animated 2D wizard face via react-native-svg (hat, head, beard, eyes, pupils, mouth arc, magic orb) - AnimatedPupils: pupilScaleAnim drives actual rendered pupil Circle radius (BASE_PUPIL_R * scale) - AnimatedEyelids: eyeScaleYAnim drives top eyelid overlay via Animated.Value listener - AnimatedMouth: smileAnim + mouthOscAnim combined; SVG Path rebuilt on each frame via listener - speaking mood: 1Hz mouth oscillation via Animated.loop; per-mood body bob speed/amplitude - @react-native-voice/voice installed and statically imported; Voice.onSpeechResults wired properly - startMicPulse/stopMicPulse declared before native voice useEffect (correct hook order) - Web Speech API typed with SpeechRecognitionWindow local interface (zero `any` casts) - sendVisitorMessage() called on final transcript (also triggers thinking mood immediately) - expo-speech TTS speaks Timmy's chat replies on native ## Matrix tab (app/(tabs)/matrix.tsx) - URL normalization: strips existing protocol, uses http for localhost, https for all other hosts - Full-screen WebView with loading spinner and error/retry state; iframe fallback for web ## Feed tab (app/(tabs)/feed.tsx) - FlatList<WsEvent> with proper generics; EventConfig discriminated union (Feather|MaterialCommunityIcons) - Icon names typed via React.ComponentProps["name"] (no `any`) - Color-coded events; event count in header; empty state with connection-aware message ## Type safety - TypeScript typecheck passes with 0 errors - No `any` casts anywhere in new code ## Deviations - expo-av removed (not used; voice input handled via @react-native-voice/voice + Web Speech API) - expo-speech/expo-av NOT in app.json plugins (no config plugins — causes PluginError if listed) - app.json extra.apiDomain added for env-driven domain configuration per requirement - expo-speech pinned ~14.0.8, react-native-webview 13.15.0 for Expo SDK 54 compat - artifact.toml ensurePreviewReachable removed (Expo uses expo-domain router) - @react-native-voice/voice works in Expo Go Android; iOS needs native build (graceful fallback) Replit-Task-Id: 0748cbbf-7b84-4149-8fc0-9d697287a0e6
2026-03-19 23:55:16 +00:00
/>
</Tabs>
);
}
export default function TabLayout() {
if (isLiquidGlassAvailable()) {\n return (\n <NativeTabs>\n <NativeTabs.Screen\n name=\"index\"\n options={{\n title: \"Timmy\",\n headerShown: true,\n headerRight: () => (\n <Link href=\"/settings\" asChild>\n <Pressable style={({ pressed }) => ({ opacity: pressed ? 0.5 : 1 })}>\n <Ionicons name=\"settings-outline\" size={24} color={C.text} style={{ marginRight: 15 }} />\n </Pressable>\n </Link>\n ),\n }}\n />\n <NativeTabs.Screen name=\"matrix\" />\n <NativeTabs.Screen name=\"feed\" />\n </NativeTabs>\n );\n }
return <ClassicTabLayout />;\
feat(mobile): scaffold Expo mobile app for Timmy with Face/Matrix/Feed tabs Task #42 — Timmy Harness: Expo Mobile App ## What was built - New Expo artifact at artifacts/mobile, slug `mobile`, preview path `/mobile/` - Three-tab bottom navigation (Face, Matrix, Feed) — NativeTabs with liquid glass on iOS 26+ - Dark wizard theme (#0A0A12 background, #7C3AED accent) ## WebSocket context (context/TimmyContext.tsx) - Full WebSocket connection to /api/ws with exponential backoff reconnect (1s→30s cap) - Sends visitor_enter handshake on connect, handles ping/pong - Derives timmyMood from agent_state events (idle/thinking/working/speaking) - recentEvents list capped at 100 - sendVisitorMessage() sets mood to "thinking" immediately on send (deterministic waiting state) - speaking mood auto-reverts after estimated TTS duration ## Face tab (app/(tabs)/index.tsx) - Animated 2D wizard face via react-native-svg (hat, head, beard, eyes, pupils, mouth arc, magic orb) - AnimatedPupils: pupilScaleAnim drives actual rendered pupil Circle radius (BASE_PUPIL_R * scale) - AnimatedEyelids: eyeScaleYAnim drives top eyelid overlay via Animated.Value listener - AnimatedMouth: smileAnim + mouthOscAnim combined; SVG Path rebuilt on each frame via listener - speaking mood: 1Hz mouth oscillation via Animated.loop; per-mood body bob speed/amplitude - @react-native-voice/voice installed and statically imported; Voice.onSpeechResults wired properly - startMicPulse/stopMicPulse declared before native voice useEffect (correct hook order) - Web Speech API typed with SpeechRecognitionWindow local interface (zero `any` casts) - sendVisitorMessage() called on final transcript (also triggers thinking mood immediately) - expo-speech TTS speaks Timmy's chat replies on native ## Matrix tab (app/(tabs)/matrix.tsx) - URL normalization: strips existing protocol, uses http for localhost, https for all other hosts - Full-screen WebView with loading spinner and error/retry state; iframe fallback for web ## Feed tab (app/(tabs)/feed.tsx) - FlatList<WsEvent> with proper generics; EventConfig discriminated union (Feather|MaterialCommunityIcons) - Icon names typed via React.ComponentProps["name"] (no `any`) - Color-coded events; event count in header; empty state with connection-aware message ## Type safety - TypeScript typecheck passes with 0 errors - No `any` casts anywhere in new code ## Deviations - expo-av removed (not used; voice input handled via @react-native-voice/voice + Web Speech API) - expo-speech/expo-av NOT in app.json plugins (no config plugins — causes PluginError if listed) - app.json extra.apiDomain added for env-driven domain configuration per requirement - expo-speech pinned ~14.0.8, react-native-webview 13.15.0 for Expo SDK 54 compat - artifact.toml ensurePreviewReachable removed (Expo uses expo-domain router) - @react-native-voice/voice works in Expo Go Android; iOS needs native build (graceful fallback) Replit-Task-Id: 0748cbbf-7b84-4149-8fc0-9d697287a0e6
2026-03-19 23:55:16 +00:00
}