Skip to content

feat: add voice input feature with transcription support#2344

Draft
princeaden1 wants to merge 4 commits intodyad-sh:mainfrom
princeaden1:new-voice-to-text
Draft

feat: add voice input feature with transcription support#2344
princeaden1 wants to merge 4 commits intodyad-sh:mainfrom
princeaden1:new-voice-to-text

Conversation

@princeaden1
Copy link
Collaborator

@princeaden1 princeaden1 commented Jan 24, 2026

closes #1804


Open with Devin

Note

Introduces voice input across chat inputs with transcription via Dyad Engine and Pro gating.

  • Replaces send row with LexicalVoiceInputRow in ChatInput and HomeChatInput, adding mic control, waveform (VoiceWaveform), and send/cancel integration
  • New VoiceInputButton handles Pro-only disabled state, recording/transcribing states, and tooltips
  • New hooks useAudioRecorder and useVoiceInput to record via MediaRecorder, visualize with AnalyserNode, and call ipc.misc.transcribeAudio
  • IPC: adds misc.transcribeAudio contract, registers transcription_handlers that validate input, support E2E mock, and call transcribeWithDyadEngine
  • Dyad Engine util: adds transcribeWithDyadEngine with request-id attempt tracking and multipart upload to /audio/transcriptions
  • E2E tests for voice flow and Pro gating; mocks getUserMedia and asserts transcription append

Written by Cursor Bugbot for commit 7dc1944. This will update automatically on new commits. Configure here.


Summary by cubic

Adds voice input with waveform visualization and transcription for chat, gated to Dyad Pro users. Improves reliability with fixes for recording setup leaks, analyser state, and proper audio MIME typing so IPC-backed Dyad Engine transcription consistently appends text; addresses #1804.

  • New Features

    • Integrated VoiceInputButton and VoiceWaveform via LexicalVoiceInputRow in ChatInput and HomeChatInput; appends transcribed text to the input.
    • Added useAudioRecorder/useVoiceInput hooks to record via MediaRecorder, visualize with AnalyserNode, and invoke IPC channel chat:transcribe.
    • Pro gating with tooltip and disabled state for non-Pro users; recording can always be stopped.
    • IPC handler validates payloads and calls Dyad Engine via multipart upload; includes E2E mock support.
    • E2E tests mock getUserMedia and verify transcription append and Pro-only disabled state.
  • Migration

    • Provide a Dyad Pro API key (settings or DYAD_PRO_API_KEY) and enable Dyad Pro.
    • Ensure microphone permissions are granted.
    • Optionally set DYAD_ENGINE_URL; defaults to https://engine.dyad.sh/v1.

Written for commit fa71433. Summary will update on new commits.

@wwwillchen
Copy link
Collaborator

@BugBot run

@github-actions
Copy link
Contributor

github-actions bot commented Jan 24, 2026

All contributors have signed the CLA ✍️ ✅
Posted by the CLA Assistant Lite bot.

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @princeaden1, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request delivers a significant new voice input capability, enabling users to interact with the application using spoken commands and queries. It encompasses a full-stack implementation, from front-end UI components for recording and real-time audio visualization to a robust backend integration for speech-to-text transcription. The feature is carefully designed with user experience in mind, including clear visual cues for recording and transcription states, and is gated as a Pro-only offering, complete with dedicated end-to-end tests to ensure its reliability.

Highlights

  • Voice Input Integration: Introduced a new voice input feature allowing users to record audio and have it transcribed directly into the chat input field.
  • Real-time Waveform Visualization: Added a dynamic waveform display that visualizes audio input during recording, enhancing user feedback.
  • Dedicated UI Components: Implemented VoiceInputButton and VoiceWaveform components for managing the voice input UI and user experience.
  • Audio Recording & Transcription Logic: Developed useAudioRecorder and useVoiceInput hooks to handle audio capture, processing, and sending data for transcription via an IPC channel.
  • Backend Transcription Service: Integrated with a Dyad Engine via new IPC handlers to perform the actual speech-to-text transcription.
  • Pro Feature Gating & E2E Testing: Ensured the voice input is a Pro-only feature with appropriate UI states and added comprehensive end-to-end tests, including mocking audio input for CI environments.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@github-actions
Copy link
Contributor

Multi-Agent Code Review Summary

Review Method: 3 independent Claude agents reviewed this PR with files in different randomized orders. Issues below were identified by 2+ agents with at least one rating MEDIUM or higher severity.

Issues Found

Severity Issue File Agents
🔴 HIGH Missing API key validation transcription_handlers.ts:27 2/3
🟡 MEDIUM Analyser ref returns stale null useAudioRecorder.ts:99 3/3
🟡 MEDIUM Direct IPC access bypasses IpcClient useAudioRecorder.ts:134 3/3

See inline comments for detailed explanations and suggested fixes.


Generated by multi-agent PR review system

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Jan 24, 2026

Greptile Overview

Greptile Summary

This PR adds a voice input feature to chat inputs by introducing LexicalVoiceInputRow (mic button + waveform) and hooks to record audio via MediaRecorder and request transcription via a new IPC contract (misc.transcribeAudio / chat:transcribe). On the main process side it registers a new transcription handler that validates payloads, supports an E2E mock response, and sends the audio to Dyad Engine’s /audio/transcriptions endpoint via multipart form upload.

Key integration points:

  • Renderer: ChatInput and HomeChatInput now render LexicalVoiceInputRow instead of a plain LexicalChatInput send-row.
  • IPC: miscContracts.transcribeAudio is added and registerTranscriptionHandlers() is wired into registerIpcHandlers().
  • Engine: transcribeWithDyadEngine is added alongside existing Dyad Engine provider utilities.
  • Tests: E2E spec validates the record→waveform→stop→append-transcript flow and Pro gating.

Issues to fix before merge are mainly around correctness/robustness of transcription request construction and validation (format handling, base64 size limit, and baseURL optionality in the engine helper).

Confidence Score: 3/5

  • This PR is close to mergeable but has a few correctness issues that can break transcription in real environments.
  • Score reduced due to definite runtime/correctness problems: baseURL optionality causing throws in transcribeWithDyadEngine, format hardcoded to webm in the renderer, and an inaccurate base64 size cap that can allow larger decoded buffers than intended.
  • src/ipc/utils/llm_engine_provider.ts, src/hooks/useAudioRecorder.ts, src/ipc/handlers/transcription_handlers.ts

Important Files Changed

Filename Overview
e2e-tests/voice_transcription.spec.ts Adds E2E coverage for voice record/transcribe flow (Pro) and Pro-gated disabled state; relies on mock getUserMedia.
src/components/chat/ChatInput.tsx Replaces LexicalChatInput row with LexicalVoiceInputRow, wiring send/cancel and disabling send during recording/transcribing.
src/components/chat/HomeChatInput.tsx Integrates LexicalVoiceInputRow into home chat input; retains attachments and streaming-disabled submit behavior.
src/components/chat/LexicalVoiceInputRow.tsx New component combining Lexical input with voice recording UI; appends transcription into input and disables send while voice is busy.
src/components/chat/VoiceInputButton.tsx Adds Pro-gated mic button with tooltip/title changes for recording/transcribing states; ensures stop remains clickable.
src/components/chat/VoiceWaveform.tsx Adds canvas-based frequency bar visualization driven by an AnalyserNode while recording.
src/hooks/useAudioRecorder.ts Introduces MediaRecorder-based audio capture and a useVoiceInput wrapper that base64-encodes audio and calls ipc.misc.transcribeAudio.
src/ipc/handlers/transcription_handlers.ts Registers new IPC handler for chat:transcribe with zod validation, E2E mock response, and Dyad Engine transcription call.
src/ipc/ipc_host.ts Registers transcription IPC handlers in the main IPC host registration list.
src/ipc/utils/llm_engine_provider.ts Adds transcribeWithDyadEngine helper using multipart FormData + request id header; potential robustness issues if baseURL is omitted.
src/ipc/types/misc.ts Adds miscContracts.transcribeAudio IPC contract (chat:transcribe) with base64 payload and string response.

Sequence Diagram

sequenceDiagram
  participant User
  participant Renderer as Renderer (ChatInput/HomeChatInput)
  participant Hook as useVoiceInput/useAudioRecorder
  participant IPC as ipc.misc.transcribeAudio
  participant Main as Main Process (transcription_handlers)
  participant Engine as Dyad Engine (/audio/transcriptions)

  User->>Renderer: Click mic button
  Renderer->>Hook: handleMicClick()
  Hook->>Hook: navigator.mediaDevices.getUserMedia(audio)
  Hook->>Hook: new MediaRecorder(stream).start()
  Hook-->>Renderer: isRecording=true, analyser set
  Renderer->>Renderer: Show VoiceWaveform(analyser)

  User->>Renderer: Click stop
  Renderer->>Hook: handleMicClick()
  Hook->>Hook: mediaRecorder.stop()
  Hook->>Hook: onstop => Blob(chunks)
  Hook->>IPC: transcribeAudio({audioData: base64, format})
  IPC->>Main: IPC chat:transcribe
  Main->>Main: validate payload + size/format
  alt E2E_TEST_BUILD=true
    Main-->>IPC: return "E2E transcribed text"
  else
    Main->>Engine: POST multipart (file, model)
    Engine-->>Main: {text}
    Main-->>IPC: return text
  end
  IPC-->>Hook: transcribed text
  Hook-->>Renderer: appendText(text)
  Renderer->>Renderer: Update Lexical input value
Loading

Copy link
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 potential issue.

View issue and 5 additional flags in Devin Review.

Open in Devin Review

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a voice input and transcription feature, a significant enhancement to the user interface, covering frontend components, hooks, backend IPC handlers, and end-to-end tests. However, a critical security weakness was identified in the IPC handler where untrusted input from the renderer is not properly validated, potentially leading to a Denial of Service (DoS) via memory exhaustion. It is recommended to use existing Zod contracts for runtime validation to mitigate this risk. Additionally, the review suggests improvements for UI consistency, performance, and code maintainability, including using the styled tooltip, optimizing waveform drawing logic, and refactoring duplicated code in chat input components.

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: abf03bdd80

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

7 issues found across 10 files

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="src/components/chat/VoiceWaveform.tsx">

<violation number="1" location="src/components/chat/VoiceWaveform.tsx:38">
P2: Avoid creating the canvas gradient inside the per-bar loop; it should be created once per frame and reused to prevent unnecessary allocations during animation.</violation>
</file>

<file name="src/ipc/handlers/transcription_handlers.ts">

<violation number="1" location="src/ipc/handlers/transcription_handlers.ts:16">
P2: The IPC handler accepts `audioData` without runtime validation or size limits. Use the Zod schema defined in `src/ipc/types/misc.ts` (`miscContracts.transcribeAudio`) to validate and parse the input. Consider adding a maximum length constraint on `audioData` to prevent memory exhaustion from excessively large payloads.</violation>

<violation number="2" location="src/ipc/handlers/transcription_handlers.ts:30">
P2: Validate the renderer-provided `format` before using it in the filename; otherwise a crafted format can inject path separators or unsupported extensions into the multipart upload filename.</violation>
</file>

<file name="src/components/chat/ChatInput.tsx">

<violation number="1" location="src/components/chat/ChatInput.tsx:489">
P2: Disable sending while voice recording/transcription is active; otherwise users can submit while recording and the mic button disappears once streaming starts, leaving the recording running with no way to stop it.</violation>
</file>

<file name="src/hooks/useAudioRecorder.ts">

<violation number="1" location="src/hooks/useAudioRecorder.ts:46">
P2: Missing `onerror` handler on MediaRecorder. If a recording error occurs mid-session (e.g., microphone disconnected), the MediaRecorder becomes inactive but `isRecording` state remains true and the timer continues. Subsequent calls to `stopRecording()` will throw `InvalidStateError` on the inactive recorder. Add an `onerror` handler to clean up state and notify the user.</violation>

<violation number="2" location="src/hooks/useAudioRecorder.ts:93">
P1: Add cleanup effect to stop active recording resources on component unmount. The hook creates a setInterval timer, MediaRecorder, and AudioContext but has no useEffect cleanup. If a user navigates away while recording, these resources continue running, causing memory leaks and potential React state-update warnings on unmounted components.</violation>

<violation number="3" location="src/hooks/useAudioRecorder.ts:169">
P2: Race condition when double-clicking the mic button. The `handleMicClick` checks `isRecording` state, but `startRecording` contains an async `getUserMedia()` call before `setIsRecording(true)`. Rapid clicks during the permission dialog can start multiple recordings, orphaning the first MediaRecorder/AudioContext and causing resource leaks. Add a guard (e.g., `isStartingRef`) set synchronously before the async call.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

@wwwillchen
Copy link
Collaborator

@BugBot run

@github-actions
Copy link
Contributor

Multi-Agent Code Review Summary

This PR was reviewed by 3 independent Claude sub-agents. Issues are reported below where 2+ agents agreed and at least one rated the issue MEDIUM severity or higher.

Issues Found: 5


🔴 HIGH: Missing IPC client method for transcribeAudio

Consensus: 3/3 agents identified this issue
Severities: HIGH, HIGH, HIGH
File: src/ipc/ipc_client.ts (not in diff)

Description: The code calls ipc.misc.transcribeAudio() in useAudioRecorder.ts, but no corresponding client-side method is visible in the diff. Per the project's IPC architecture (CLAUDE.md), IpcClient should expose dedicated methods per IPC channel. Without this method, the voice input feature will fail at runtime.

Suggestion: Verify that a transcribeAudio method exists in IpcClient.getInstance() that invokes the 'chat:transcribe' channel.


🔴 HIGH: Missing preload allowlist entry for transcription IPC channel

Consensus: 3/3 agents identified this issue
Severities: HIGH, HIGH, HIGH
File: src/preload.ts (not in diff)

Description: According to CLAUDE.md: "New IPC APIs must be added to src/preload.ts". The new 'chat:transcribe' IPC channel is not shown in the diff. Without adding this channel to the preload allowlist, the renderer process will not be able to invoke the transcription handler, causing the feature to fail.

Suggestion: Add the 'chat:transcribe' channel to the allowlist in src/preload.ts.


🟡 MEDIUM: Request ID tracking never cleans up completed requests

Consensus: 3/3 agents identified this issue
Severities: MEDIUM, MEDIUM, LOW
File: src/ipc/utils/llm_engine_provider.ts

Description: The transcriptionRequestIdAttempts Map tracks request attempts but only removes entries when the map exceeds MAX_TRACKED_REQUESTS (500). Completed transcriptions never have their entries removed, leading to gradual memory accumulation. Additionally, if the same requestId were reused, the attempt counter would incorrectly continue from the previous value.

Suggestion: Consider removing entries after successful transcription, or implement time-based expiration.


🟡 MEDIUM: MediaRecorder error handler doesn't propagate errors to users

Consensus: 2/3 agents identified this issue
Severities: MEDIUM, MEDIUM
File: src/hooks/useAudioRecorder.ts (lines ~88-101)

Description: When MediaRecorder.onerror fires, it logs the error and resets state but doesn't call any error callback or notify the user. The recording silently fails, leaving the user wondering why nothing happened. This is especially problematic for codec issues, mid-recording permission revocations, or hardware problems.

Suggestion: Accept an optional onError callback in useAudioRecorder and call it in the mediaRecorder.onerror handler to notify users of recording failures.


🟡 MEDIUM: FileReader error handling has race conditions

Consensus: 2/3 agents identified this issue
Severities: HIGH, MEDIUM
File: src/hooks/useAudioRecorder.ts (lines ~175-216)

Description: In handleRecordingComplete, when FileReader.onerror fires, it calls onError and setIsTranscribing(false), but doesn't prevent the onload handler from potentially being called afterwards. This can lead to duplicate error messages or confusing UI states if both handlers execute.

Suggestion: Wrap the FileReader logic in a Promise and properly reject on error, or add a flag to prevent onload from executing after onerror fires.


🤖 Generated by Multi-Agent PR Review using 3 independent Claude sub-agents with consensus voting

@wwwillchen
Copy link
Collaborator

@BugBot run

Copy link
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 new potential issue.

View issue and 7 additional flags in Devin Review.

Open in Devin Review

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

} catch (err) {
logger.error("Error starting recording:", err);
throw err;
} finally {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stream leaked when recording setup fails after getUserMedia

Medium Severity

The catch block in startRecording re-throws without cleaning up resources obtained earlier in the try block. If getUserMedia succeeds (line 64) and streamRef.current is set (line 66), but then new AudioContext() or new MediaRecorder() throws, the stream is never stopped. On retry, a new stream overwrites streamRef.current, orphaning the original. The microphone remains active for the orphaned stream with no way to release it until component unmount or page close.

Fix in Cursor Fix in Web

}

return currentAttempt;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Request ID attempt tracking serves no purpose

Low Severity

The transcriptionRequestIdAttempts map and trackRequestAttempt function track retry attempts for request IDs, but each transcription call generates a fresh UUID via uuidv4() in transcription_handlers.ts. Since request IDs are never reused, the attempt count will always be 1, making this tracking mechanism dead code that adds complexity without benefit.

Additional Locations (1)

Fix in Cursor Fix in Web

@github-actions
Copy link
Contributor

🤖 Multi-Agent Code Review Summary

PR: feat: add voice input feature with transcription support

Three independent Claude review agents analyzed this PR. Issues are reported only when 2+ agents identified them and at least one rated it MEDIUM or higher severity.

Issues Found: 3

Severity File Description Agents
🟡 MEDIUM src/hooks/useAudioRecorder.ts:164 Analyser ref returns stale/null value 3/3
🟡 MEDIUM src/hooks/useAudioRecorder.ts:154 Race condition with duplicate setIsRecording(false) calls 2/3
🟡 MEDIUM src/ipc/utils/llm_engine_provider.ts:282 Blob created without MIME type 3/3

See inline comments for details and suggested fixes.


Review performed by 3 Claude agents with randomized file ordering to reduce bias.

audioBlob,
startRecording,
stopRecording,
analyser: analyserRef.current,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Multi-Agent Review Finding (3/3 agents, MEDIUM severity)

Issue: The analyser property returns analyserRef.current directly, which is captured at render time. When recording starts, analyserRef.current is updated inside startRecording, but the returned analyser in the component will still be null until a re-render occurs. This causes the VoiceWaveform component to receive null initially when recording begins.

Suggestion: Consider storing the analyser in React state instead of only in a ref, or return the ref itself and access .current in the consuming component to ensure the value is always current when isRecording is true.

logger.warn("Failed to stop media recorder", error);
}
}
setIsRecording(false);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Multi-Agent Review Finding (2/3 agents, MEDIUM severity)

Issue: Race condition - setIsRecording(false) is called here immediately, but the mediaRecorder.onstop callback (line 112) also calls setIsRecording(false). This creates a duplicate state update where isRecording may be set to false before the recording data is actually finalized and the onRecordingComplete callback is called, potentially causing UI inconsistencies.

Suggestion: Remove setIsRecording(false) from stopRecording and let the onstop handler manage the state transition after the blob is created and onRecordingComplete is called.

}

const formData = new FormData();
const blob = new Blob([audioBuffer as any]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Multi-Agent Review Finding (3/3 agents, MEDIUM severity)

Issue: The Blob is created with audioBuffer as any without specifying a MIME type. This may cause issues with the transcription API as the server expects a properly typed audio file, but the Blob is created without a type property.

Suggestion: Add the MIME type to the Blob constructor based on the filename extension:

Suggested change
const blob = new Blob([audioBuffer as any]);
const mimeType = filename.endsWith('.webm') ? 'audio/webm' :
filename.endsWith('.mp3') ? 'audio/mpeg' :
filename.endsWith('.wav') ? 'audio/wav' :
filename.endsWith('.m4a') ? 'audio/mp4' : 'audio/webm';
const blob = new Blob([audioBuffer], { type: mimeType });

@wwwillchen
Copy link
Collaborator

@BugBot run

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 2 files (changes from recent commits).

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="src/hooks/useAudioRecorder.ts">

<violation number="1" location="src/hooks/useAudioRecorder.ts:75">
P1: This state update may run after the component has unmounted (e.g. if the user navigates away while the permission prompt is visible). If unmounted, the media resources (stream and audio context) must be cleaned up to prevent the microphone from staying active (resource leak).</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

@github-actions
Copy link
Contributor

github-actions bot commented Feb 5, 2026

🤖 Multi-Agent PR Review Summary

Three independent AI agents reviewed this PR, each analyzing the code from different perspectives to reduce ordering bias.

Consensus Issues Found

Issues reported below were identified by 2+ agents with at least one rating the issue as MEDIUM or higher severity.

Severity File Issue
MEDIUM src/ipc/utils/llm_engine_provider.ts:275 Response JSON parsed without validation
MEDIUM src/hooks/useAudioRecorder.ts:216 setIsTranscribing(false) can update state after unmount

Issue Details

1. Response JSON validation missing (3/3 agents, all MEDIUM)

In transcribeWithDyadEngine, the API response is cast to { text: string } without validation. If the API returns an unexpected format, data.text could be undefined, violating the function's return type.

2. State update on unmounted component (2/3 agents, one MEDIUM)

In useVoiceInput, setIsTranscribing(false) is called unconditionally after the async transcription completes. If the component unmounts during transcription, this will attempt to update state on an unmounted component.


Review performed by 3 independent Claude agents with consensus voting (2+ agreement required)

`Dyad Engine transcription failed: ${response.status} ${response.statusText} - ${errorText}`,
);
}
const data = (await response.json()) as { text: string };
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Multi-Agent Review (3/3 agents, all MEDIUM severity)

The response JSON is cast to { text: string } without validation. If the API returns an unexpected format (e.g., error object, empty object, or different structure), data.text could be undefined, leading to silent failures.

Suggested change
const data = (await response.json()) as { text: string };
const data = (await response.json()) as { text?: string };
if (!data || typeof data.text !== 'string') {
throw new Error('Invalid transcription response: missing text field');
}
return data.text;

err instanceof Error ? err.message : "Failed to transcribe audio",
);
}
setIsTranscribing(false);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Multi-Agent Review (2/3 agents, one MEDIUM severity)

setIsTranscribing(false) is called unconditionally after the async transcription completes. If the component unmounts during transcription, this will attempt to update state on an unmounted component.

Consider:

  1. Adding an isMounted ref to useVoiceInput (similar to how useAudioRecorder uses isMountedRef)
  2. Or moving this into a finally block with a mount check
Suggested change
setIsTranscribing(false);
setIsTranscribing(false);
},
[appendText, onError],
);

Note: The suggestion above keeps the current structure. To fix properly, add an isMountedRef to useVoiceInput and check it before calling setIsTranscribing(false).

- Fix stream leak when recording setup fails after getUserMedia
- Fix analyser ref returning stale null by using React state
- Fix race condition with duplicate setIsRecording(false) calls
- Remove dead request ID attempt tracking code
- Add MIME type to Blob in transcription for proper audio typing

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@wwwillchen
Copy link
Collaborator

@BugBot run

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

11 files reviewed, 3 comments

Edit Code Review Agent Settings | Greptile

Comment on lines +236 to +244
const baseURL = withoutTrailingSlash(options.baseURL);
const apiKey = loadApiKey({
apiKey: options.apiKey,
environmentVariableName: "DYAD_PRO_API_KEY",
description: "Dyad Pro API key",
});
logger.info("transcribing with dyad engine with baseURL", baseURL);

const formData = new FormData();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing baseURL crashes

transcribeWithDyadEngine calls withoutTrailingSlash(options.baseURL) but baseURL is optional in ExampleProviderSettings (baseURL?: string). If options.baseURL is undefined, this will throw before any network request. This can happen if another caller reuses transcribeWithDyadEngine without explicitly passing baseURL.

Also appears in createDyadEngine at src/ipc/utils/llm_engine_provider.ts:68.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/ipc/utils/llm_engine_provider.ts
Line: 236:244

Comment:
**Missing baseURL crashes**

`transcribeWithDyadEngine` calls `withoutTrailingSlash(options.baseURL)` but `baseURL` is optional in `ExampleProviderSettings` (`baseURL?: string`). If `options.baseURL` is `undefined`, this will throw before any network request. This can happen if another caller reuses `transcribeWithDyadEngine` without explicitly passing `baseURL`.

Also appears in `createDyadEngine` at `src/ipc/utils/llm_engine_provider.ts:68`.

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +202 to +205
const text = await ipc.misc.transcribeAudio({
audioData: base64Content,
format: "webm",
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wrong audio format sent

useVoiceInput always passes format: "webm" to ipc.misc.transcribeAudio (src/hooks/useAudioRecorder.ts:202-205), even though the recorded Blob’s actual MIME/type can vary by browser/platform. This can lead to the main-process handler rejecting the request as an unsupported format (or producing a mismatched filename/MIME downstream).

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/hooks/useAudioRecorder.ts
Line: 202:205

Comment:
**Wrong audio format sent**

`useVoiceInput` always passes `format: "webm"` to `ipc.misc.transcribeAudio` (`src/hooks/useAudioRecorder.ts:202-205`), even though the recorded Blob’s actual MIME/type can vary by browser/platform. This can lead to the main-process handler rejecting the request as an unsupported format (or producing a mismatched filename/MIME downstream).

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +21 to +24
if (audioData.length > MAX_AUDIO_BASE64_LENGTH) {
throw new Error("Audio payload exceeds maximum allowed size.");
}
const normalizedFormat = format.trim().toLowerCase();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Base64 size check wrong

The handler checks audioData.length against MAX_AUDIO_BASE64_LENGTH (src/ipc/handlers/transcription_handlers.ts:21-22), but base64 expands binary by ~33%. As written, the decoded Buffer.from(audioData, "base64") can be significantly larger than the intended cap, impacting memory usage.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/ipc/handlers/transcription_handlers.ts
Line: 21:24

Comment:
**Base64 size check wrong**

The handler checks `audioData.length` against `MAX_AUDIO_BASE64_LENGTH` (`src/ipc/handlers/transcription_handlers.ts:21-22`), but base64 expands binary by ~33%. As written, the decoded `Buffer.from(audioData, "base64")` can be significantly larger than the intended cap, impacting memory usage.

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 new potential issue.

View issue and 10 additional flags in Devin Review.

Open in Devin Review

Comment on lines +54 to +60
<button
data-testid="voice-input-button"
onClick={proModeTogglable || isRecording ? onClick : undefined}
className={className}
aria-label={title}
disabled={isDisabled}
/>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 VoiceInputButton missing title attribute causes E2E test failures

The E2E tests expect the voice input button to have a title attribute, but the component only sets aria-label.

Click to expand

E2E Test Expectations

The E2E tests in e2e-tests/voice_transcription.spec.ts assert on the title attribute:

await expect(voiceBtn).toHaveAttribute("title", "Start voice input");
await expect(voiceBtn).toHaveAttribute("title", "Stop recording");
await expect(voiceBtn).toHaveAttribute("title", "Pro feature only");

Actual Implementation

In src/components/chat/VoiceInputButton.tsx:54-60, the button only sets aria-label:

<button
  data-testid="voice-input-button"
  onClick={proModeTogglable || isRecording ? onClick : undefined}
  className={className}
  aria-label={title}  // <-- Only aria-label is set
  disabled={isDisabled}
/>

Impact

All voice transcription E2E tests will fail because the title attribute is never set on the button element. The tests at lines 49, 57, 60, and 81 of voice_transcription.spec.ts will not find the expected title attribute.

Recommendation: Add title={title} to the button element alongside aria-label={title}:

<button
  data-testid="voice-input-button"
  onClick={proModeTogglable || isRecording ? onClick : undefined}
  className={className}
  aria-label={title}
  title={title}
  disabled={isDisabled}
/>
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@github-actions
Copy link
Contributor

github-actions bot commented Feb 5, 2026

🔍 Multi-Agent Code Review

Found 2 new issue(s) flagged by 3 independent reviewers using consensus voting.
(Many issues were already identified by previous reviewers and are not repeated here)

Summary

Severity Count
🔴 HIGH 1
🟡 MEDIUM 1

New Issues to Address

Severity File Issue
🔴 HIGH src/ipc/handlers/transcription_handlers.ts:14 Handler bypasses contract-driven IPC architecture
🟡 MEDIUM src/hooks/useAudioRecorder.ts:195 setIsTranscribing(false) should be in finally block

See inline comments for details.


ℹ️ Issues already addressed by existing review comments

The following issues were identified by our multi-agent review but have already been flagged by other reviewers:

  • API key validation before transcription request (transcription_handlers.ts)
  • Input validation and Zod schema constraints (transcription_handlers.ts, misc.ts)
  • Analyser ref returns stale null value (useAudioRecorder.ts)
  • Race condition in handleMicClick (useAudioRecorder.ts)
  • MediaRecorder error handler improvements (useAudioRecorder.ts)
  • Cleanup on component unmount (useAudioRecorder.ts)
  • Gradient created on every animation frame (VoiceWaveform.tsx)
  • Stream cleanup on recording setup failure (useAudioRecorder.ts)
  • Memory leak in request ID tracking map (llm_engine_provider.ts)

Generated by multi-agent consensus review (3 agents)

const MAX_AUDIO_BASE64_LENGTH = 10 * 1024 * 1024;
const ALLOWED_AUDIO_FORMATS = new Set(["webm", "mp3", "wav", "m4a"]);

export function registerTranscriptionHandlers() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 HIGH: Handler bypasses contract-driven IPC architecture (3/3 agents)

This handler uses raw ipcMain.handle directly instead of createTypedHandler from the project's contract-driven IPC architecture. According to the project's CLAUDE.md:

Handlers should use createTypedHandler(contract, handler) which validates inputs at runtime via Zod.

The current implementation manually parses the input with miscContracts.transcribeAudio.input.parse(input) inside a try/catch, but this is inconsistent with all other handlers in src/ipc/handlers/*.ts which use the standardized createTypedHandler pattern.

Suggestion: Refactor to use the established pattern:

Suggested change
export function registerTranscriptionHandlers() {
export function registerTranscriptionHandlers() {
createTypedHandler(
miscContracts.transcribeAudio,

Then update the handler signature and move the logic into the typed handler body. This provides:

  • Consistent runtime Zod validation
  • Standardized error handling and logging
  • Type-safe handler implementation

const content = base64data.split(",")[1];
if (!content) {
reject(new Error("Invalid audio data format"));
return;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 MEDIUM: setIsTranscribing(false) should be in finally block (2/3 agents)

The setIsTranscribing(false) call is placed after the try/catch block, which works correctly now. However, this is fragile - if any future modification adds a re-throw in the catch block or if an error occurs in the catch block itself, isTranscribing would remain true indefinitely, leaving the UI in a stuck state.

Suggestion: Move setIsTranscribing(false) to a finally block for robustness:

  const handleRecordingComplete = useCallback(
    async (blob: Blob) => {
      setIsTranscribing(true);
      try {
        // ... existing logic ...
      } catch (err) {
        logger.error("Transcription failed", err);
        onError?.(
          err instanceof Error ? err.message : "Failed to transcribe audio",
        );
      } finally {
        setIsTranscribing(false);
      }
    },
    [appendText, onError],
  );

This ensures isTranscribing is always reset regardless of success or failure paths.

@github-actions
Copy link
Contributor

github-actions bot commented Feb 5, 2026

🎭 Playwright Test Results

❌ Some tests failed

OS Passed Failed Flaky Skipped
🍎 macOS 267 52 0 108
🪟 Windows 269 17 2 108

Summary: 536 passed, 69 failed, 2 flaky, 216 skipped

Failed Tests

🍎 macOS

Show all 52 failures
  • annotator.spec.ts > annotator - capture and submit screenshot
    • TimeoutError: locator.fill: Timeout 30000ms exceeded.
  • astro.spec.ts > astro
    • TimeoutError: locator.fill: Timeout 30000ms exceeded.
  • attach_image.spec.ts > attach image - chat
    • TimeoutError: locator.fill: Timeout 30000ms exceeded.
  • attach_image.spec.ts > attach image - chat - upload to codebase
    • TimeoutError: locator.fill: Timeout 30000ms exceeded.
  • attach_image.spec.ts > attach image via drag - chat
    • TimeoutError: locator.fill: Timeout 30000ms exceeded.
  • chat_history.spec.ts > should open, navigate, and select from history menu
    • TimeoutError: locator.fill: Timeout 30000ms exceeded.
  • chat_history.spec.ts > should handle edge cases: guards, escape, and sending after cancel
    • TimeoutError: locator.fill: Timeout 30000ms exceeded.
  • chat_input.spec.ts > send button disabled during pending proposal
    • TimeoutError: locator.fill: Timeout 30000ms exceeded.
  • chat_input.spec.ts > send button disabled during pending proposal - reject
    • TimeoutError: locator.fill: Timeout 30000ms exceeded.
  • chat_mode.spec.ts > chat mode selector - default build mode
    • TimeoutError: locator.fill: Timeout 30000ms exceeded.
  • chat_mode.spec.ts > chat mode selector - ask mode
    • TimeoutError: locator.click: Timeout 30000ms exceeded.
  • context_window.spec.ts > context window
    • TimeoutError: locator.fill: Timeout 30000ms exceeded.
  • copy_chat.spec.ts > copy message content - basic functionality
    • TimeoutError: locator.fill: Timeout 30000ms exceeded.
  • copy_chat.spec.ts > copy message content - dyad-write conversion
    • TimeoutError: locator.fill: Timeout 30000ms exceeded.
  • dyad_tags_parsing.spec.ts > dyad tags handles nested < tags
    • TimeoutError: locator.fill: Timeout 30000ms exceeded.
  • free_agent_quota.spec.ts > free agent quota - full flow: mode availability, quota tracking, exceeded banner, switch to build
    • TimeoutError: locator.fill: Timeout 30000ms exceeded.
  • free_agent_quota.spec.ts > free agent quota - quota resets after 24 hours
    • TimeoutError: locator.fill: Timeout 30000ms exceeded.
  • local_agent_advanced.spec.ts > local-agent - mcp tool call
    • TimeoutError: locator.fill: Timeout 30000ms exceeded.
  • local_agent_ask.spec.ts > local-agent ask mode
    • TimeoutError: locator.fill: Timeout 30000ms exceeded.
  • local_agent_auto.spec.ts > local-agent - auto model
    • TimeoutError: locator.fill: Timeout 30000ms exceeded.
  • local_agent_basic.spec.ts > local-agent - dump request
    • TimeoutError: locator.fill: Timeout 30000ms exceeded.
  • local_agent_basic.spec.ts > local-agent - read then edit
    • TimeoutError: locator.fill: Timeout 30000ms exceeded.
  • local_agent_basic.spec.ts > local-agent - parallel tool calls
    • TimeoutError: locator.fill: Timeout 30000ms exceeded.
  • local_agent_code_search.spec.ts > local-agent - code search
    • TimeoutError: locator.fill: Timeout 30000ms exceeded.
  • local_agent_consent.spec.ts > local-agent - add_dependency consent: always allow
    • TimeoutError: locator.fill: Timeout 30000ms exceeded.
  • local_agent_consent.spec.ts > local-agent - add_dependency consent: allow once
    • TimeoutError: locator.fill: Timeout 30000ms exceeded.
  • local_agent_consent.spec.ts > local-agent - add_dependency consent: decline
    • TimeoutError: locator.fill: Timeout 30000ms exceeded.
  • local_agent_file_upload.spec.ts > local-agent - upload file to codebase
    • TimeoutError: locator.fill: Timeout 30000ms exceeded.
  • local_agent_grep.spec.ts > local-agent - grep search
    • TimeoutError: locator.fill: Timeout 30000ms exceeded.
  • local_agent_list_files.spec.ts > local-agent - list_files
    • TimeoutError: locator.fill: Timeout 30000ms exceeded.
  • local_agent_read_logs.spec.ts > local-agent - read logs with filters
    • TimeoutError: locator.fill: Timeout 30000ms exceeded.
  • local_agent_run_type_checks.spec.ts > local-agent - run_type_checks updates problems panel
    • TimeoutError: locator.fill: Timeout 30000ms exceeded.
  • local_agent_search_replace.spec.ts > local-agent - search_replace edit
    • TimeoutError: locator.fill: Timeout 30000ms exceeded.
  • local_agent_summarize.spec.ts > local-agent - summarize to new chat works
    • TimeoutError: locator.fill: Timeout 30000ms exceeded.
  • mcp.spec.ts > mcp - call calculator
    • TimeoutError: locator.fill: Timeout 30000ms exceeded.
  • mcp.spec.ts > mcp - call calculator via http
    • TimeoutError: locator.fill: Timeout 30000ms exceeded.
  • new_chat.spec.ts > new chat (first button)
    • TimeoutError: locator.fill: Timeout 30000ms exceeded.
  • new_chat.spec.ts > new chat (second button)
    • TimeoutError: locator.fill: Timeout 30000ms exceeded.
  • partial_response.spec.ts > partial message is resumed
    • TimeoutError: locator.fill: Timeout 30000ms exceeded.
  • rename_edit.spec.ts > rename then edit works
    • TimeoutError: locator.fill: Timeout 30000ms exceeded.
  • select_component.spec.ts > select component
    • TimeoutError: locator.fill: Timeout 30000ms exceeded.
  • select_component.spec.ts > select multiple components
    • TimeoutError: locator.fill: Timeout 30000ms exceeded.
  • select_component.spec.ts > deselect component
    • TimeoutError: locator.fill: Timeout 30000ms exceeded.
  • select_component.spec.ts > select component next.js
    • Error: expect(locator).toMatchAriaSnapshot(expected) failed
  • smart_context_deep.spec.ts > smart context deep - read write read
    • TimeoutError: locator.fill: Timeout 30000ms exceeded.
  • supabase_branch.spec.ts > supabase branch selection works
    • TimeoutError: locator.fill: Timeout 30000ms exceeded.
  • supabase_client.spec.ts > supabase client is generated
    • TimeoutError: locator.fill: Timeout 30000ms exceeded.
  • undo.spec.ts > undo
    • TimeoutError: locator.fill: Timeout 30000ms exceeded.
  • undo.spec.ts > undo with native git
    • TimeoutError: locator.fill: Timeout 30000ms exceeded.
  • undo.spec.ts > undo after assistant with no code
    • TimeoutError: locator.fill: Timeout 30000ms exceeded.
  • voice_transcription.spec.ts > voice transcription - home chat: record, transcribe, append to input
    • Error: expect(locator).toHaveAttribute(expected) failed
  • voice_transcription.spec.ts > voice input - non-Pro user sees Pro-only disabled state
    • Error: expect(locator).toHaveAttribute(expected) failed

🪟 Windows

Show all 17 failures
  • astro.spec.ts > astro
    • TimeoutError: locator.fill: Timeout 30000ms exceeded.
  • attach_image.spec.ts > attach image - chat
    • TimeoutError: locator.fill: Timeout 30000ms exceeded.
  • attach_image.spec.ts > attach image - chat - upload to codebase
    • TimeoutError: locator.fill: Timeout 30000ms exceeded.
  • attach_image.spec.ts > attach image via drag - chat
    • TimeoutError: locator.fill: Timeout 30000ms exceeded.
  • chat_history.spec.ts > should open, navigate, and select from history menu
    • TimeoutError: locator.fill: Timeout 30000ms exceeded.
  • chat_history.spec.ts > should handle edge cases: guards, escape, and sending after cancel
    • TimeoutError: locator.fill: Timeout 30000ms exceeded.
  • chat_input.spec.ts > send button disabled during pending proposal
    • TimeoutError: locator.fill: Timeout 30000ms exceeded.
  • chat_input.spec.ts > send button disabled during pending proposal - reject
    • TimeoutError: locator.fill: Timeout 30000ms exceeded.
  • chat_mode.spec.ts > chat mode selector - default build mode
    • TimeoutError: locator.fill: Timeout 30000ms exceeded.
  • chat_mode.spec.ts > chat mode selector - ask mode
    • TimeoutError: locator.fill: Timeout 30000ms exceeded.
  • copy_chat.spec.ts > copy message content - basic functionality
    • TimeoutError: locator.fill: Timeout 30000ms exceeded.
  • copy_chat.spec.ts > copy message content - dyad-write conversion
    • TimeoutError: locator.fill: Timeout 30000ms exceeded.
  • new_chat.spec.ts > new chat (first button)
    • TimeoutError: locator.fill: Timeout 30000ms exceeded.
  • new_chat.spec.ts > new chat (second button)
    • TimeoutError: locator.fill: Timeout 30000ms exceeded.
  • partial_response.spec.ts > partial message is resumed
    • TimeoutError: locator.fill: Timeout 30000ms exceeded.
  • rename_edit.spec.ts > rename then edit works
    • TimeoutError: locator.fill: Timeout 30000ms exceeded.
  • voice_transcription.spec.ts > voice input - non-Pro user sees Pro-only disabled state
    • Error: expect(locator).toHaveAttribute(expected) failed

📋 Test Commands (macOS)

Copy and paste to re-run failing tests locally:

Show all 52 test commands
export PLAYWRIGHT_HTML_OPEN=never

# annotator.spec.ts > annotator - capture and submit screenshot
# Expected: TimeoutError: locator.fill: Timeout 30000ms exceeded.
npm run e2e e2e-tests/annotator.spec.ts -- -g "annotator - capture and submit screenshot"

# astro.spec.ts > astro
# Expected: TimeoutError: locator.fill: Timeout 30000ms exceeded.
npm run e2e e2e-tests/astro.spec.ts -- -g "astro"

# attach_image.spec.ts > attach image - chat
# Expected: TimeoutError: locator.fill: Timeout 30000ms exceeded.
npm run e2e e2e-tests/attach_image.spec.ts -- -g "attach image - chat"

# attach_image.spec.ts > attach image - chat - upload to codebase
# Expected: TimeoutError: locator.fill: Timeout 30000ms exceeded.
npm run e2e e2e-tests/attach_image.spec.ts -- -g "attach image - chat - upload to codebase"

# attach_image.spec.ts > attach image via drag - chat
# Expected: TimeoutError: locator.fill: Timeout 30000ms exceeded.
npm run e2e e2e-tests/attach_image.spec.ts -- -g "attach image via drag - chat"

# chat_history.spec.ts > should open, navigate, and select from history menu
# Expected: TimeoutError: locator.fill: Timeout 30000ms exceeded.
npm run e2e e2e-tests/chat_history.spec.ts -- -g "should open, navigate, and select from history menu"

# chat_history.spec.ts > should handle edge cases: guards, escape, and sending after cancel
# Expected: TimeoutError: locator.fill: Timeout 30000ms exceeded.
npm run e2e e2e-tests/chat_history.spec.ts -- -g "should handle edge cases: guards, escape, and sending after cancel"

# chat_input.spec.ts > send button disabled during pending proposal
# Expected: TimeoutError: locator.fill: Timeout 30000ms exceeded.
npm run e2e e2e-tests/chat_input.spec.ts -- -g "send button disabled during pending proposal"

# chat_input.spec.ts > send button disabled during pending proposal - reject
# Expected: TimeoutError: locator.fill: Timeout 30000ms exceeded.
npm run e2e e2e-tests/chat_input.spec.ts -- -g "send button disabled during pending proposal - reject"

# chat_mode.spec.ts > chat mode selector - default build mode
# Expected: TimeoutError: locator.fill: Timeout 30000ms exceeded.
npm run e2e e2e-tests/chat_mode.spec.ts -- -g "chat mode selector - default build mode"

# chat_mode.spec.ts > chat mode selector - ask mode
# Expected: TimeoutError: locator.click: Timeout 30000ms exceeded.
npm run e2e e2e-tests/chat_mode.spec.ts -- -g "chat mode selector - ask mode"

# context_window.spec.ts > context window
# Expected: TimeoutError: locator.fill: Timeout 30000ms exceeded.
npm run e2e e2e-tests/context_window.spec.ts -- -g "context window"

# copy_chat.spec.ts > copy message content - basic functionality
# Expected: TimeoutError: locator.fill: Timeout 30000ms exceeded.
npm run e2e e2e-tests/copy_chat.spec.ts -- -g "copy message content - basic functionality"

# copy_chat.spec.ts > copy message content - dyad-write conversion
# Expected: TimeoutError: locator.fill: Timeout 30000ms exceeded.
npm run e2e e2e-tests/copy_chat.spec.ts -- -g "copy message content - dyad-write conversion"

# dyad_tags_parsing.spec.ts > dyad tags handles nested < tags
# Expected: TimeoutError: locator.fill: Timeout 30000ms exceeded.
npm run e2e e2e-tests/dyad_tags_parsing.spec.ts -- -g "dyad tags handles nested < tags"

# free_agent_quota.spec.ts > free agent quota - full flow: mode availability, quota tracking, exceeded banner, switch to build
# Expected: TimeoutError: locator.fill: Timeout 30000ms exceeded.
npm run e2e e2e-tests/free_agent_quota.spec.ts -- -g "free agent quota - full flow: mode availability, quota tracking, exceeded banner, switch to build"

# free_agent_quota.spec.ts > free agent quota - quota resets after 24 hours
# Expected: TimeoutError: locator.fill: Timeout 30000ms exceeded.
npm run e2e e2e-tests/free_agent_quota.spec.ts -- -g "free agent quota - quota resets after 24 hours"

# local_agent_advanced.spec.ts > local-agent - mcp tool call
# Expected: TimeoutError: locator.fill: Timeout 30000ms exceeded.
npm run e2e e2e-tests/local_agent_advanced.spec.ts -- -g "local-agent - mcp tool call"

# local_agent_ask.spec.ts > local-agent ask mode
# Expected: TimeoutError: locator.fill: Timeout 30000ms exceeded.
npm run e2e e2e-tests/local_agent_ask.spec.ts -- -g "local-agent ask mode"

# local_agent_auto.spec.ts > local-agent - auto model
# Expected: TimeoutError: locator.fill: Timeout 30000ms exceeded.
npm run e2e e2e-tests/local_agent_auto.spec.ts -- -g "local-agent - auto model"

# local_agent_basic.spec.ts > local-agent - dump request
# Expected: TimeoutError: locator.fill: Timeout 30000ms exceeded.
npm run e2e e2e-tests/local_agent_basic.spec.ts -- -g "local-agent - dump request"

# local_agent_basic.spec.ts > local-agent - read then edit
# Expected: TimeoutError: locator.fill: Timeout 30000ms exceeded.
npm run e2e e2e-tests/local_agent_basic.spec.ts -- -g "local-agent - read then edit"

# local_agent_basic.spec.ts > local-agent - parallel tool calls
# Expected: TimeoutError: locator.fill: Timeout 30000ms exceeded.
npm run e2e e2e-tests/local_agent_basic.spec.ts -- -g "local-agent - parallel tool calls"

# local_agent_code_search.spec.ts > local-agent - code search
# Expected: TimeoutError: locator.fill: Timeout 30000ms exceeded.
npm run e2e e2e-tests/local_agent_code_search.spec.ts -- -g "local-agent - code search"

# local_agent_consent.spec.ts > local-agent - add_dependency consent: always allow
# Expected: TimeoutError: locator.fill: Timeout 30000ms exceeded.
npm run e2e e2e-tests/local_agent_consent.spec.ts -- -g "local-agent - add_dependency consent: always allow"

# local_agent_consent.spec.ts > local-agent - add_dependency consent: allow once
# Expected: TimeoutError: locator.fill: Timeout 30000ms exceeded.
npm run e2e e2e-tests/local_agent_consent.spec.ts -- -g "local-agent - add_dependency consent: allow once"

# local_agent_consent.spec.ts > local-agent - add_dependency consent: decline
# Expected: TimeoutError: locator.fill: Timeout 30000ms exceeded.
npm run e2e e2e-tests/local_agent_consent.spec.ts -- -g "local-agent - add_dependency consent: decline"

# local_agent_file_upload.spec.ts > local-agent - upload file to codebase
# Expected: TimeoutError: locator.fill: Timeout 30000ms exceeded.
npm run e2e e2e-tests/local_agent_file_upload.spec.ts -- -g "local-agent - upload file to codebase"

# local_agent_grep.spec.ts > local-agent - grep search
# Expected: TimeoutError: locator.fill: Timeout 30000ms exceeded.
npm run e2e e2e-tests/local_agent_grep.spec.ts -- -g "local-agent - grep search"

# local_agent_list_files.spec.ts > local-agent - list_files
# Expected: TimeoutError: locator.fill: Timeout 30000ms exceeded.
npm run e2e e2e-tests/local_agent_list_files.spec.ts -- -g "local-agent - list_files"

# local_agent_read_logs.spec.ts > local-agent - read logs with filters
# Expected: TimeoutError: locator.fill: Timeout 30000ms exceeded.
npm run e2e e2e-tests/local_agent_read_logs.spec.ts -- -g "local-agent - read logs with filters"

# local_agent_run_type_checks.spec.ts > local-agent - run_type_checks updates problems panel
# Expected: TimeoutError: locator.fill: Timeout 30000ms exceeded.
npm run e2e e2e-tests/local_agent_run_type_checks.spec.ts -- -g "local-agent - run_type_checks updates problems panel"

# local_agent_search_replace.spec.ts > local-agent - search_replace edit
# Expected: TimeoutError: locator.fill: Timeout 30000ms exceeded.
npm run e2e e2e-tests/local_agent_search_replace.spec.ts -- -g "local-agent - search_replace edit"

# local_agent_summarize.spec.ts > local-agent - summarize to new chat works
# Expected: TimeoutError: locator.fill: Timeout 30000ms exceeded.
npm run e2e e2e-tests/local_agent_summarize.spec.ts -- -g "local-agent - summarize to new chat works"

# mcp.spec.ts > mcp - call calculator
# Expected: TimeoutError: locator.fill: Timeout 30000ms exceeded.
npm run e2e e2e-tests/mcp.spec.ts -- -g "mcp - call calculator"

# mcp.spec.ts > mcp - call calculator via http
# Expected: TimeoutError: locator.fill: Timeout 30000ms exceeded.
npm run e2e e2e-tests/mcp.spec.ts -- -g "mcp - call calculator via http"

# new_chat.spec.ts > new chat (first button)
# Expected: TimeoutError: locator.fill: Timeout 30000ms exceeded.
npm run e2e e2e-tests/new_chat.spec.ts -- -g "new chat \(first button\)"

# new_chat.spec.ts > new chat (second button)
# Expected: TimeoutError: locator.fill: Timeout 30000ms exceeded.
npm run e2e e2e-tests/new_chat.spec.ts -- -g "new chat \(second button\)"

# partial_response.spec.ts > partial message is resumed
# Expected: TimeoutError: locator.fill: Timeout 30000ms exceeded.
npm run e2e e2e-tests/partial_response.spec.ts -- -g "partial message is resumed"

# rename_edit.spec.ts > rename then edit works
# Expected: TimeoutError: locator.fill: Timeout 30000ms exceeded.
npm run e2e e2e-tests/rename_edit.spec.ts -- -g "rename then edit works"

# select_component.spec.ts > select component
# Expected: TimeoutError: locator.fill: Timeout 30000ms exceeded.
npm run e2e e2e-tests/select_component.spec.ts -- -g "select component"

# select_component.spec.ts > select multiple components
# Expected: TimeoutError: locator.fill: Timeout 30000ms exceeded.
npm run e2e e2e-tests/select_component.spec.ts -- -g "select multiple components"

# select_component.spec.ts > deselect component
# Expected: TimeoutError: locator.fill: Timeout 30000ms exceeded.
npm run e2e e2e-tests/select_component.spec.ts -- -g "deselect component"

# select_component.spec.ts > select component next.js
# Expected: Error: expect(locator).toMatchAriaSnapshot(expected) failed
npm run e2e e2e-tests/select_component.spec.ts -- -g "select component next\.js" --update-snapshots

# smart_context_deep.spec.ts > smart context deep - read write read
# Expected: TimeoutError: locator.fill: Timeout 30000ms exceeded.
npm run e2e e2e-tests/smart_context_deep.spec.ts -- -g "smart context deep - read write read"

# supabase_branch.spec.ts > supabase branch selection works
# Expected: TimeoutError: locator.fill: Timeout 30000ms exceeded.
npm run e2e e2e-tests/supabase_branch.spec.ts -- -g "supabase branch selection works"

# supabase_client.spec.ts > supabase client is generated
# Expected: TimeoutError: locator.fill: Timeout 30000ms exceeded.
npm run e2e e2e-tests/supabase_client.spec.ts -- -g "supabase client is generated"

# undo.spec.ts > undo
# Expected: TimeoutError: locator.fill: Timeout 30000ms exceeded.
npm run e2e e2e-tests/undo.spec.ts -- -g "undo"

# undo.spec.ts > undo with native git
# Expected: TimeoutError: locator.fill: Timeout 30000ms exceeded.
npm run e2e e2e-tests/undo.spec.ts -- -g "undo with native git"

# undo.spec.ts > undo after assistant with no code
# Expected: TimeoutError: locator.fill: Timeout 30000ms exceeded.
npm run e2e e2e-tests/undo.spec.ts -- -g "undo after assistant with no code"

# voice_transcription.spec.ts > voice transcription - home chat: record, transcribe, append to input
# Expected: Error: expect(locator).toHaveAttribute(expected) failed
npm run e2e e2e-tests/voice_transcription.spec.ts -- -g "voice transcription - home chat: record, transcribe, append to input"

# voice_transcription.spec.ts > voice input - non-Pro user sees Pro-only disabled state
# Expected: Error: expect(locator).toHaveAttribute(expected) failed
npm run e2e e2e-tests/voice_transcription.spec.ts -- -g "voice input - non-Pro user sees Pro-only disabled state"

⚠️ Flaky Tests

🪟 Windows

  • context_manage.spec.ts > manage context - exclude paths with smart context (passed after 2 retries)
  • setup_flow.spec.ts > Setup Flow > node.js install flow (passed after 1 retry)

📊 View full report

@princeaden1 princeaden1 marked this pull request as draft February 5, 2026 17:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support voice input

2 participants