---
phase: 36-voice-pipeline-foundation
plan: 03
subsystem: api
tags: [express, multer, voice, whisper, piper, sse, tts, stt]

# Dependency graph
requires:
  - phase: 36-01
    provides: voicePipelineService with transcribe/synthesize/formatForVoice
  - phase: 36-02
    provides: voiceMode field on createMessageSchema and ChatMessage interface

provides:
  - POST /api/transcribe HTTP endpoint (audio upload → VoicePipelineService.transcribe)
  - POST /api/synthesize HTTP endpoint (text body → VoicePipelineService.synthesize → audio/wav)
  - voiceMode flag wired through chat stream endpoint with dual-output prompt injection (VPIPE-06)
  - voiceMode persisted to messageType column on assistant message save
  - Old inline /transcribe endpoint removed from chat-files.ts

affects:
  - phase-37-voice-web-ui (uses POST /api/transcribe and POST /api/synthesize from browser)
  - phase-38-telegram-bridge (uses POST /api/transcribe and POST /api/synthesize from Telegram)

# Tech tracking
tech-stack:
  added: []
  patterns:
    - "voiceRoutes() factory function pattern matching chatFileRoutes()"
    - "multer.memoryStorage() for audio upload in dedicated voice route"
    - "dual-output SSE prompt injection — full_voice mode appends SPOKEN:/DETAILED: system message"
    - "voiceMode-to-messageType mapping: full_voice→voice_full, voice_input→voice_input"

key-files:
  created:
    - server/src/routes/voice.ts
    - server/src/__tests__/36-voice-routes.test.ts
  modified:
    - server/src/routes/chat.ts
    - server/src/routes/chat-files.ts
    - server/src/app.ts

key-decisions:
  - "Voice routes are a dedicated module (voice.ts) rather than added to chat-files.ts for clean separation of concerns"
  - "assertBoard(req) on both voice endpoints — same auth pattern as all other board-facing API routes"
  - "voiceMode destructured as typed union in stream endpoint to enable compile-time safety"

patterns-established:
  - "Dual-output prompt: SPOKEN: section for TTS delivery, DETAILED: section for full markdown"
  - "messageType column stores voice_full/voice_input for downstream rendering decisions"

requirements-completed: [VPIPE-03, VPIPE-06]

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

# Phase 36 Plan 03: Voice HTTP Routes + voiceMode Wiring Summary

**Voice pipeline HTTP-accessible via POST /api/transcribe and POST /api/synthesize, with full_voice dual-output prompt injection and messageType persistence in the SSE stream endpoint**

## Performance

- **Duration:** 25 min
- **Started:** 2026-04-04T01:33:00Z
- **Completed:** 2026-04-04T01:58:00Z
- **Tasks:** 2
- **Files modified:** 5

## Accomplishments

- Created `server/src/routes/voice.ts` with POST /transcribe (multer audio upload → VoicePipelineService) and POST /synthesize (text → audio/wav buffer), both protected by `assertBoard(req)`
- Wired `voiceMode` through the chat stream endpoint: full_voice triggers dual-output SPOKEN:/DETAILED: system prompt injection; voice_input/full_voice persist to `messageType` column
- Removed 90-line inline `/transcribe` implementation from `chat-files.ts` and mounted `voiceRoutes()` in `app.ts`
- 5 integration tests pass covering both success and error cases for both endpoints

## Task Commits

1. **Task 1: Create voice.ts routes and tests** - `b49b0aa5` (feat + TDD)
2. **Task 2: Wire voiceMode in chat.ts, mount voice routes, remove old transcribe** - `3fc1ac10` (feat)

## Files Created/Modified

- `server/src/routes/voice.ts` — New voice routes factory: POST /transcribe and POST /synthesize
- `server/src/__tests__/36-voice-routes.test.ts` — 5 integration tests for both endpoints
- `server/src/routes/chat.ts` — voiceMode destructured, dual-output prompt injected, messageType persisted
- `server/src/routes/chat-files.ts` — Removed old inline /transcribe endpoint (90 lines removed)
- `server/src/app.ts` — Imported voiceRoutes and mounted with api.use(voiceRoutes())

## Decisions Made

- Voice routes are a dedicated `voice.ts` module rather than added to `chat-files.ts` for clean separation — voice pipeline is its own subsystem
- Both voice endpoints call `assertBoard(req)` for consistent auth with all other board-facing routes
- `voiceMode` typed as `"text" | "voice_input" | "full_voice"` union in stream endpoint for compile-time correctness

## Deviations from Plan

None — plan executed exactly as written.

Note: Pre-existing TypeScript errors in `voice-pipeline.ts` (from Plan 36-01, ffmpeg-static type mismatch) were not caused by this plan's changes. Files modified in this plan compile clean.

## Issues Encountered

- Worktree node_modules resolution: vitest from the worktree couldn't resolve `express`/`supertest` because the worktree has an empty local `node_modules`. Fixed by creating symlinks from the worktree's `node_modules` and `server/node_modules` to the main repo's equivalents. This is a one-time setup for the parallel worktree environment.

## User Setup Required

None — no external service configuration required.

## Next Phase Readiness

- Voice pipeline is fully HTTP-accessible from any transport (web, Telegram, CLI)
- Phase 37 (voice web UI) can use POST /api/transcribe and POST /api/synthesize directly
- Phase 38 (Telegram bridge) can use the same endpoints for voice message relay
- voiceMode flag flows end-to-end: client request body → dual-output prompt → messageType persistence

## Self-Check: PASSED

- voice.ts: FOUND
- 36-voice-routes.test.ts: FOUND
- 36-03-SUMMARY.md: FOUND
- commit b49b0aa5: FOUND
- commit 3fc1ac10: FOUND

---
*Phase: 36-voice-pipeline-foundation*
*Completed: 2026-04-04*
