opencode/packages/mobile-voice/refactor.md

3.5 KiB

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.