---
phase: 34-voice
plan: 02
subsystem: ui, onboarding
tags: [voice, onboarding, tts, stt, piper, whisper, personal-assistant]

# Dependency graph
requires:
  - phase: 34-voice/34-01
    provides: usePiperTts, TtsButton, VoiceRecordButton, voiceEnabled nexus-settings
  - phase: 33-persistent-memory
    provides: PersonalAssistant page, chatApi streaming
  - phase: 32-multi-step-onboarding-wizard
    provides: NexusOnboardingWizard 5-step base

provides:
  - VoiceStep onboarding component with mic detection (enumerateDevices)
  - 6-step NexusOnboardingWizard with voice opt-in at step 4
  - voiceEnabled persisted in nexus-settings on workspace creation
  - PersonalAssistant with VoiceRecordButton (STT) in input bar
  - PersonalAssistant with TtsButton next to each assistant message (TTS)

affects: [ui/src/components/NexusOnboardingWizard.tsx, ui/src/pages/PersonalAssistant.tsx]

# Tech tracking
tech-stack:
  added: []
  patterns:
    - "VoiceStep probes microphone with navigator.mediaDevices.enumerateDevices() — async, graceful fallback to false"
    - "voiceEnabled captured in wizard state, persisted via updateNexusSettings after createWorkspace()"
    - "TtsButton rendered inline in messages.map (no prop drilling) — pl-10 aligns under message bubble"
    - "VoiceRecordButton appends transcription to textarea (not auto-send) — user reviews before sending"

key-files:
  created:
    - ui/src/components/onboarding/VoiceStep.tsx
  modified:
    - ui/src/components/NexusOnboardingWizard.tsx
    - ui/src/pages/PersonalAssistant.tsx

key-decisions:
  - "VoiceStep inserted at step 4; rootDir shifts to step 5, summary to step 6 — clean sequential numbering"
  - "voiceEnabled persisted after mode save in createWorkspace() — non-blocking try/catch wrapper"
  - "TtsButton rendered inline in messages.map rather than inside MessageBubble — avoids prop drilling usePiperTts through MessageBubble"
  - "VoiceRecordButton appends (not replaces) transcription to textarea — user can combine typed + spoken input"
  - "No TTS auto-prewarm on mount — triggered only on first TtsButton click to avoid unexpected WASM downloads"

requirements-completed: [VOICE-03]

# Metrics
duration: 4min
completed: 2026-04-03
tasks_completed: 2
files_changed: 3
---

# Phase 34 Plan 02: Voice Onboarding Step + PersonalAssistant Wire-up Summary

**VoiceStep onboarding component (mic detection, enable/skip) inserted as wizard step 4; VoiceRecordButton (STT) and TtsButton (TTS) wired into PersonalAssistant for full voice I/O**

## Performance

- **Duration:** ~4 min
- **Started:** 2026-04-03T22:38:32Z
- **Completed:** 2026-04-03T22:41:13Z
- **Tasks:** 2/2
- **Files modified:** 3

## Accomplishments

### Task 1: Create VoiceStep component and insert into NexusOnboardingWizard as step 4

- Created `ui/src/components/onboarding/VoiceStep.tsx` with:
  - `navigator.mediaDevices.enumerateDevices()` mic probe with loading/available/unavailable states
  - Two info cards (Mic/Whisper STT + Volume/Piper TTS) with conditional mic availability message
  - Enable voice button (sets voiceEnabled = true, advances to step 5) and Skip button
- Updated `NexusOnboardingWizard.tsx`:
  - Added `VoiceStep` import
  - Added `voiceEnabled` state (default false, reset on close, persisted in `createWorkspace()`)
  - Inserted step 4 (Voice) block with Back → step 3, Enable → step 5, Skip → step 5
  - Shifted old step 4 (rootDir) to step 5 — "Review & finish" → step 6, Back → step 4 (voice), Skip to summary → step 6
  - Shifted old step 5 (summary) to step 6 — Back → step 5
  - Updated step indicator from `Step N of 4` to `Step N of 5` (summary shows "Summary" at step 6)
  - Updated handleStartChat error message to reference step 5

### Task 2: Wire VoiceRecordButton and TtsButton into PersonalAssistant

- Added imports: `VoiceRecordButton`, `TtsButton`, `usePiperTts`
- Added `usePiperTts` hook call in component body — exposes `ttsStatus`, `ttsProgress`, `prewarm`, `speak`, `stop`
- Added `VoiceRecordButton` in the input bar between textarea and Send button:
  - `onTranscription` callback appends transcribed text to textarea (does not auto-send)
  - `disabled` passes through `isSending` state
- Added `TtsButton` next to each assistant message in `messages.map`:
  - Wrapped message rendering in `<div>` to accommodate TtsButton below each bubble
  - `pl-10` aligns button under message bubble (past avatar width)
  - `-mt-1 mb-1` tucks close to message
  - Only renders for messages with `role === "assistant"` and non-empty content

## Verification

- `grep -q "VoiceStep" ui/src/components/NexusOnboardingWizard.tsx` — PASS
- `grep -q "step === 6" ui/src/components/NexusOnboardingWizard.tsx` — PASS
- `grep -q "VoiceRecordButton" ui/src/pages/PersonalAssistant.tsx` — PASS
- `grep -q "TtsButton" ui/src/pages/PersonalAssistant.tsx` — PASS
- `grep -q "enumerateDevices" ui/src/components/onboarding/VoiceStep.tsx` — PASS

## Deviations from Plan

None — plan executed exactly as written.

## Known Stubs

None — all voice features are wired. TtsButton requires user to click for first download (intentional — avoids unexpected 50MB WASM download on page load).

## Commits

| Commit | Task | Description |
|--------|------|-------------|
| be8c1f67 | Task 1 | feat(34-02): create VoiceStep component and insert into NexusOnboardingWizard as step 4 |
| 263a19e4 | Task 2 | feat(34-02): wire VoiceRecordButton and TtsButton into PersonalAssistant |

## Self-Check: PASSED
