PeachJam parity research · 26 July 2026

Voice mode, mapped and working.

Direct Lovable evidence, a full interaction graph, an approved OpenRouter transcription path, and live desktop + iPhone 17 Pro proof in PeachJam’s shared composer.

Decision: ship a scoped STT subsystem.

Capture stays in the browser. One dedicated authenticated endpoint sends only the recording to openai/whisper-large-v3-turbo through PeachJam’s existing OpenRouter gateway. The builder prompt contract is unchanged.

Frontend build passed Provider smoke passed Desktop passed iPhone 17 Pro passed No Build-credit debit

Working PeachJam proof

These recordings exercise the actual PeachJam composer and browser MediaRecorder. Chromium’s virtual microphone played a public 3.4-second speech WAV; the transcript came from the approved live OpenRouter gateway. The surrounding project data is a deterministic UI fixture, not a production project.

Desktop · recording waveform → transcribing → appended transcript → discard → permission-denied recovery.
Installed iPhone 17 Pro profile · 402×681 CSS viewport, DPR 3.
3.4 shonest input audio duration
10 sprovider-reported billable duration
$0.000111provider-reported live-smoke cost

The complete Lovable state graph

Captured against authenticated Lovable on both /dashboard and project chat. “Apply” means use the transcript as draft text; it does not submit the prompt.

01 · Idle

Normal composer

Editor, model control, microphone, and send control are visible.

02 · Requesting

Await permission

Draft remains visible. Normal trailing controls are replaced immediately by Discard and Apply.

03 · Recording

Honest waveform

The editor is replaced by analyser-driven bars. Attach/actions remain. Discard and Apply stay visible.

04 · Apply

Stop capture

MediaRecorder finalizes the blob. Tracks and AudioContext are released.

05 · Transcribing

Draft restored

Inline “Transcribing...” appears. Discard and Apply are visible but disabled.

06A · Text

Append result

Empty draft becomes transcript. Existing draft gets two newlines, then transcript.

06B · Empty

No speech

An empty {text:""} response restores idle without changing the draft or showing an error.

06C · Failure

Retryable

Draft remains. Inline failure appears. Controls become Discard recording and Retry transcription.

Permission denied

Return to idle

Helpful GUI message: “Failed to access microphone. Please check permissions.”

Discard

Local only

Stops capture, preserves the draft, and sends no transcription request.

60-second cap

Ready to apply

PeachJam stops capture, freezes the waveform, and asks the user to Apply or Discard.

Back to idle

Editable draft

The transcript is still only draft text. Sending remains a separate explicit action.

SurfaceShared behaviorMeasured differencePeachJam translation
DashboardPermission swap, waveform, Discard/Apply, STT response shapeWider waveform (128 bars at 638 px); 32 px recording controlsSame shared component; bar count comes from measured width
Project chatSame interaction states and exact audio request field82 bars at 408 px; 28 px desktop controlsSame state machine; project layout owns the available width
Dark themeSame state transitionsLighter waveform; light Apply control on dark composerNamed light/dark voice tokens, no blanket primitive restyle
MobileSame shared state graphComposer stays bottom-aligned; controls remain touch-sizedVerified with installed iPhone 17 Pro profile

Implementation boundary

This is deliberately not a general component migration and not a builder-provider payload change.

Browser micMediaRecorder + Web Audio analyser
PeachJam APIPOST /v1/user/speech-to-text
OpenRouter/audio/transcriptions

Browser → PeachJam

  • Authenticated multipart request with one field: audio.
  • WebM/Ogg/MP4/MP3/WAV, maximum 4 MiB.
  • Audio exists in memory for the request only; PeachJam does not persist it.
  • Browser receives only {text:string}.

PeachJam → provider

  • Existing OpenRouter API key and Cloudflare AI Gateway token.
  • Multipart fields: file, model, response_format=json.
  • Provider errors are typed and upstream bodies are not exposed to users.
  • Request outcome, latency, audio seconds, and USD cost are emitted as low-cardinality telemetry.

Limits, credits, and cost

ConcernPolicyReason
Recording duration60 seconds client-sideBounds wait time and cost while covering ordinary prompt dictation.
Payload4 MiB server-sideMuch tighter than OpenRouter’s documented 25 MB maximum.
ConcurrencyOne active transcription per actorPrevents accidental double-apply and duplicated spend.
Rate10/minute and 100/rolling 24 hours per actorAbuse guard without turning ordinary iteration into friction.
Build creditsNo debit for voice transcriptionVoice is an input method. Normal Build credits apply only after explicit prompt submission.
Provider billingPlatform operating cost, measured from provider responseThe live 3.4-second sample was billed at the provider’s 10-second minimum: $0.000111.

Known scaling constraint: per-actor rate state is process-local in this first slice. It is correct for the current single API instance; it must move to a shared durable limiter before horizontal API scaling.

Provider references: OpenRouter speech-to-text guide and Groq speech-to-text pricing/limits. The measured 10-second billable duration matches Groq’s documented minimum.

Direct Lovable evidence

No prompt-text assertions: screenshots, DOM/computed-style inspection, live MediaRecorder behavior, request/response bodies, and an intentionally aborted request were used to map observable behavior.

Verification

Passed

  • Frontend TypeScript + Vite production build.
  • OpenAPI generation into the frontend contract.
  • Provider multipart/header/usage/error unit tests.
  • Service concurrency and minute/daily limit tests.
  • Authenticated HTTP alias, missing audio, and oversize tests.
  • Desktop and iPhone 17 Pro browser transitions.
  • Live gateway smoke with the same approved model.

Evidence boundary

  • The videos use an honest virtual microphone and live transcription, not a hardcoded transcript.
  • The demo’s surrounding project/chat API data is a deterministic fixture.
  • The direct gateway smoke and backend contract are separately verified.
  • No production deployment is claimed by this report until the change lands on origin/main.