Mobile Voice Refactor Plan
Goals
- Reduce the surface area of
src/app/index.tsx without changing product behavior.
- Make device, network, and monitoring flows easier to reason about.
- Move toward React Native / Expo best practices for state, effects, and file structure.
- Use the new lint warnings as refactor prompts, not as permanent background noise.
Current Pain Points
DictationScreen currently owns onboarding, permissions, Whisper/model lifecycle, dictation, pairing, server/session sync, relay registration, notification handling, and most UI rendering.
- The screen mixes render-time derived state, imperative refs, polling, persistence, and native cleanup in one place.
- There are many nested conditionals and long derived blocks that are hard to scan.
- Best-effort async cleanup and silent catches make failures harder to understand.
Target Shape
src/app/index.tsx
- compose hooks and presentational sections
- keep only screen-level orchestration
src/features/onboarding/
- onboarding step config
- onboarding UI component
src/features/dictation/
use-whisper-dictation
- transcript helpers
src/features/servers/
- server/session refresh and pairing helpers
- persisted server state helpers
src/features/monitoring/
- foreground SSE monitoring
- notification payload handling
- relay registration helpers
src/lib/
- parser/validation helpers
- logger helper for dev-only diagnostics
Refactor Order
Phase 1: Extract pure helpers first
- Move onboarding step text/style selection into a config object or array.
- Move server/session payload parsing into dedicated helpers.
- Keep existing behavior and props the same.
Phase 2: Extract onboarding UI
- Create an
OnboardingFlow component that receives explicit state and handlers.
- Keep onboarding persistence in the screen until the UI extraction is stable.
Phase 3: Extract dictation logic
- Move Whisper loading, recording, bulk/realtime transcription, and waveform state into a
useWhisperDictation hook.
- Expose a small interface: recording state, transcript, actions, and model status.
Phase 4: Extract server/session management
- Move server restore/save, pairing, health refresh, and active server/session selection into a dedicated hook.
- Centralize server parsing and dedupe logic.
Phase 5: Extract monitoring and notifications
- Move SSE monitoring, push payload handling, and relay registration into a
useMonitoring hook.
- Keep side effects close to the feature that owns them.
Phase 6: Lint burn-down
- Replace
any with explicit parsed shapes.
- Reduce nested ternaries in favor of config tables.
- Replace ad hoc
console.log calls with a logger helper or __DEV__-gated diagnostics.
- Audit bare
.catch(() => {}) and convert non-trivial cases to explicit best-effort helpers or real error handling.
Guardrails During Refactor
- Keep one behavior-preserving slice per PR.
- Do not introduce more derived state in
useEffect.
- Prefer explicit hook inputs/outputs over hidden cross-hook coupling.
- Only use refs for imperative APIs, subscriptions, and race control.
- Re-run lint after each slice.
- Validate app behavior in the dev client for microphone, notifications, pairing, and monitoring flows.
Exit Criteria
src/app/index.tsx is mostly screen composition and stays under roughly 800-1200 lines.
- Feature logic lives in focused hooks/components with clearer ownership.
- New payload parsing does not rely on
any.
- Lint warnings trend down instead of growing.