#4382 · Mobile microphone source unverified
Verdict: NOT REPRODUCED · Root-cause confidence: low
1. TL;DR
A reporter observed that voice input started on a phone appeared to transcribe sound near a laptop running the same bb server. The exact phone client and audio source were not isolated. At the trusted main commit, the composer requests audio from its own browser and sends the resulting file to the server. A two-client hook test passed twice and did not call the laptop media API when recording started in the phone context. Without a physical phone and laptop, this does not settle the reported observation or identify a root cause.
2. Claims vs findings
| Claim | Status | Evidence |
|---|---|---|
| Voice started on a phone used a separate laptop microphone. | Unverified | No physical-device run, audio recording, track label, or isolated sound-source comparison is available. |
| The composer requests audio in the initiating browser. | Verified | useVoiceInput.ts lines 263–281; the focused probe calls only the phone-context media API. |
| The server selects a microphone on the laptop. | Refuted for this route | api.ts lines 176–187 uploads a file; system.ts lines 633–647 receives that file and invokes transcription. |
| Speech near a TV by the laptop appeared in the result. | Unverified | The issue reports this observation, but no recording or controlled distance measurement permits source attribution. |
3. Environment
- Trusted source:
9c9bae7f36a237c7e1b96de3d4c2186d13967686(origin/main). - Two clean Linux x86_64 checkouts; Node 26.8.1; pnpm 9.15.0. Frozen install and Turbo build succeeded in each.
- No bb dev instance, provider, ports, or data directory were used. The focused test used jsdom and mocked browser media streams.
- Phone model, operating system, browser or mobile app build, and laptop app build remain unknown.
4. Closest repeatable reproduction
- In a clean checkout of the commit above, run
pnpm install --frozen-lockfile --prefer-offlineandpnpm exec turbo run build. - Copy voice-client-context.test.tsx into
apps/app/src/hooks/. - Run
pnpm exec turbo run test --filter=@bb/app -- voice-client-context.test.tsx. The test mounts a laptop composer, switches to a phone browser context, and starts voice input there.
Expected if the reported client-routing defect exists: laptop getUserMedia is called.
Actual, first clean checkout: 1 test passed; phone getUserMedia called with { audio: true }; laptop getUserMedia not called.
Actual, second clean checkout: 1 test passed; same assertions.
This probes the app capture path, not physical microphone selection. Existing related suites also passed: 12 device-preference tests and 7 voice-hook tests.
// @vitest-environment jsdom
import { act, cleanup, renderHook } from "@testing-library/react";
import { afterEach, expect, it, vi } from "vitest";
import { useVoiceInput } from "./useVoiceInput";
vi.mock("@/components/ui/app-toast", () => ({ appToast: { error: vi.fn() } }));
class Recorder {
static isTypeSupported = () => false;
state = "inactive";
onstart = () => {};
onstop = () => {};
ondataavailable = () => {};
onerror = () => {};
constructor(readonly stream: MediaStream) {}
start() {
this.state = "recording";
this.onstart();
}
stop() {
this.state = "inactive";
this.onstop();
}
}
afterEach(() => {
cleanup();
window.localStorage.clear();
vi.unstubAllGlobals();
vi.clearAllMocks();
});
it("captures through the browser that starts voice input", async () => {
window.localStorage.clear();
const phoneStream = { getTracks: () => [{ stop: vi.fn() }] };
const phoneGetUserMedia = vi.fn().mockResolvedValue(phoneStream);
const laptopGetUserMedia = vi.fn();
vi.stubGlobal("MediaRecorder", Recorder);
vi.stubGlobal("navigator", {
mediaDevices: { getUserMedia: laptopGetUserMedia },
});
const laptop = renderHook(() =>
useVoiceInput({ onTranscribe: vi.fn(), onTranscript: vi.fn() }),
);
vi.stubGlobal("navigator", {
mediaDevices: { getUserMedia: phoneGetUserMedia },
});
const { result, unmount } = renderHook(() =>
useVoiceInput({ onTranscribe: vi.fn(), onTranscript: vi.fn() }),
);
await act(async () => result.current.start());
expect(phoneGetUserMedia).toHaveBeenCalledExactlyOnceWith({ audio: true });
expect(laptopGetUserMedia).not.toHaveBeenCalled();
expect(result.current.stream).toBe(phoneStream);
unmount();
laptop.unmount();
});
5. Root-cause assessment
No repository root cause is established. The initiating page passes its navigator.mediaDevices into requestAudioInputStream and constructs MediaRecorder from that stream (useVoiceInput.ts lines 263–281). The preference helper uses either the local default device or a local exact device ID, with a fallback to the local default when the exact device is absent (audio-input-device-preference.ts lines 43–77). The server receives an audio file after recording (system.ts lines 633–647). The mobile WebView diagnostic probe likewise calls its own navigator.mediaDevices.getUserMedia (mobile WebView voice probe lines 189–203). These paths do not show a call that captures the laptop microphone for a phone session. The browser or operating system could choose a device unexpectedly, but the current evidence cannot establish that as the cause.
6. Next experiment
On the reporter's exact phone client and laptop build, record two distinct spoken phrases in acoustically separated locations while the phone starts voice input. Log the phone page's selected MediaStreamTrack.label and getSettings().deviceId locally, plus the laptop's active recording indicator. Save a short local recording before transcription and compare it with the uploaded result. Those observations would distinguish capture-device selection from ambient pickup or transcription confusion before changing code.
7. Related issues
#4236 concerns transcription loss after leaving a composer; it does not address microphone source. No linked open pull request was found for #4382 when this report was prepared.
8. Verification
The same agent repeated the focused test in a second clean temporary checkout at 9c9bae7f36a237c7e1b96de3d4c2186d13967686, after a frozen install and Turbo build. The command above passed again (one test). No report correction was needed. Both runs are simulations; neither used physical phones, microphones, or bb Connect. The issue content was treated as untrusted claims; no issue-provided command, link, or code was executed.
9. Appendix
Commands run for each checkout: pnpm install --frozen-lockfile --prefer-offline; pnpm exec turbo run build; pnpm exec turbo run test --filter=@bb/app -- voice-client-context.test.tsx. In the first checkout, pnpm exec turbo run test --filter=@bb/app -- useVoiceInput.test.tsx audio-input-device-preference.test.ts passed 19 tests. The first checkout needed local PWA icon regeneration because the generated assets failed their check; this did not affect the microphone test. Code permalinks above were checked against the recorded commit. No image is included because this is an audio-source claim, not a visual defect.