Commit Graph

3 Commits

Author SHA1 Message Date
Alexander Whitestone
b9a9d44da6 feat: add AppState-aware WebSocket reconnect on mobile foreground
Some checks failed
CI / Typecheck & Lint (pull_request) Failing after 0s
When the app returns from background, check if the WebSocket is still
open. If not, close the stale socket and reconnect with reset backoff.
Proactively close the WS when backgrounding to save battery and avoid
OS killing it mid-frame. Add "reconnecting" connection status with
amber pulsing badge so users see the app is re-establishing connection.

Fixes #33

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 21:50:40 -04:00
alexpaynex
f50d62332b task(#44): Mobile app validation — fix Matrix URL and tower path CWD bug
## Original task
Validate and fix the Expo mobile app (Face/Matrix/Feed tabs) against the live API server.
Restart the API server (was EADDRINUSE from prior merge), verify domain config, test all three tabs, fix issues, and confirm TypeScript typecheck passes.

## Changes made

### artifacts/mobile/app/(tabs)/matrix.tsx
- Fixed getMatrixUrl(): was returning `https://{domain}/` (API landing page), now returns `https://{domain}/tower` (Three.js 3D world). This was the main UI bug — the Matrix tab was showing the wrong page.

### artifacts/api-server/src/app.ts
- Fixed tower static file path: replaced `path.resolve(process.cwd(), "the-matrix", "dist")` with `path.resolve(__dirname_app, "../../..", "the-matrix", "dist")` using `fileURLToPath(import.meta.url)`.
- Root cause: pnpm `--filter` runs scripts from the package directory (`artifacts/api-server`), so `process.cwd()` resolved to `artifacts/api-server/the-matrix/dist` (missing), not `the-matrix/dist` at workspace root. This caused /tower to 404 in development.
- The import.meta.url approach works correctly in both dev (tsx from src/) and production (esbuild CJS bundle from dist/) since both are 3 levels deep from workspace root.

### Infrastructure
- Killed stale process on port 18115, restarted Expo workflow (was stuck waiting for port with interactive prompt).
- Restarted API server (was EADDRINUSE from prior task merge).

## Verification
- API healthz returns 200, /tower/ returns 200.
- TypeScript typecheck passes for @workspace/mobile (no errors).
- TypeScript typecheck passes for @workspace/api-server (no errors).
- Expo dev server running on port 18115, Metro bundler active.
- WebSocket connections visible in API server logs (clients connected).
- EXPO_PUBLIC_DOMAIN set to $REPLIT_DEV_DOMAIN in dev script (correct for wss:// and https:// connections).
2026-03-20 00:48:24 +00:00
alexpaynex
cf1819f34b 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