---
phase: 42-wallpapers-social-format-conversion-voice
plan: 04
subsystem: ui
tags: [voice, whisper, offline-badge, react-hook, lucide-react, typescript]

# Dependency graph
requires:
  - phase: 42-wallpapers-social-format-conversion-voice
    provides: Plan 01 foundation types and server hardware route at /api/system/providers
affects:
  - 42-06 (UI wiring — voice offline badge now visible in ChatInput)

provides:
  - useSystemProviders hook fetching /api/system/providers and exposing whisperAvailable/piperAvailable
  - Offline badge in ChatInput (WifiOff icon + "Offline" label) when local Whisper binary detected

# Tech tracking
tech-stack:
  added: []
  patterns:
    - Simple one-shot fetch hook (no SWR/React Query) for UI capability flags
    - Graceful degradation: badge absent when providers null (fetch error or Whisper not installed)

key-files:
  created:
    - ui/src/hooks/useSystemProviders.ts
  modified:
    - ui/src/components/ChatInput.tsx

key-decisions:
  - "useSystemProviders uses plain useState+useEffect fetch (not React Query) — matches plan spec and keeps hook lightweight for a one-time capability probe"
  - "Hook maps voiceCapability from HardwareInfo response shape — /api/system/providers returns full HardwareInfo, whisperAvailable lives in voiceCapability sub-object"

patterns-established:
  - "Offline badge pattern: enableVoiceInput && providers?.whisperAvailable — double guard ensures badge only shows in voice mode with confirmed local model"

requirements-completed: [VOICE-01, VOICE-02, VOICE-03]

# Metrics
duration: 2min
completed: 2026-04-04
---

# Phase 42 Plan 04: Voice Offline Badge and useSystemProviders Hook

**useSystemProviders hook wires /api/system/providers to ChatInput offline badge, surfacing local Whisper availability via WifiOff icon when voice mode is active**

## Performance

- **Duration:** 2 min
- **Started:** 2026-04-04T22:12:11Z
- **Completed:** 2026-04-04T22:14:00Z
- **Tasks:** 2
- **Files modified:** 2 (1 created, 1 modified)

## Accomplishments
- Created useSystemProviders hook that fetches /api/system/providers once on mount, extracts voiceCapability.whisperAvailable, and returns null providers on error for graceful degradation
- Added WifiOff icon offline badge to ChatInput — renders only when enableVoiceInput=true AND providers.whisperAvailable=true
- Verified VoiceMicButton and VoiceModeToggle already rendering correctly from prior phase work (no changes needed)

## Task Commits

Each task was committed atomically:

1. **Task 1: Create useSystemProviders hook** - `768f62fa` (feat)
2. **Task 2: Add offline badge to ChatInput** - `69ae0a00` (feat)

## Files Created/Modified
- `ui/src/hooks/useSystemProviders.ts` - One-shot fetch hook for system providers; exports SystemProviders interface and useSystemProviders function
- `ui/src/components/ChatInput.tsx` - Added WifiOff import, useSystemProviders call, and offline badge JSX next to VoiceMicButton

## Decisions Made
- Used plain useState+useEffect pattern (not React Query / useHardwareInfo) as the plan specified — the hook is intentionally lightweight for a one-time capability probe
- Mapped voiceCapability sub-object from HardwareInfo since the /api/system/providers endpoint returns the full hardware info shape; whisperAvailable lives at `data.voiceCapability.whisperAvailable`

## Deviations from Plan

None - plan executed exactly as written.

## Issues Encountered
None.

## User Setup Required
None - no external service configuration required.

## Next Phase Readiness
- Offline badge available in ChatInput; voice UI feature surface is now complete
- Plan 06 (UI wiring) can wire enableVoiceInput prop from user settings/mode state
- Voice mode toggle and mic button confirmed rendering correctly when enableVoiceInput=true

---
*Phase: 42-wallpapers-social-format-conversion-voice*
*Completed: 2026-04-04*
