Skip to content

Commit 3614931

Browse files
author
haotool
committed
fix(manual-submit): v0.9.3 - fix timing issue with state capture
CRITICAL FIX: Dictation state was being checked AFTER ChatGPT had already processed the click and changed the UI state. Solution: - Capture dictation state and baseline text at click time (capture phase) - Pass saved state to async handler instead of re-checking - This ensures manual submit works even when ChatGPT instantly updates UI Changes: - setupManualSubmitListener() captures state before ChatGPT processes click - handleManualSubmitClick() now accepts wasInDictationMode and savedBaselineText - Added detailed logging for debugging
1 parent 40acc90 commit 3614931

File tree

4 files changed

+50
-18
lines changed

4 files changed

+50
-18
lines changed

CHANGELOG.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,25 @@ All notable changes to EchoType will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [0.9.3] - 2026-01-21
9+
10+
### 🐛 Bug Fixes
11+
- **Manual Submit Timing Fix** - Fixed critical timing issue where dictation state was checked after ChatGPT had already processed the click:
12+
- Now captures dictation state and baseline text at the moment of click (in capture phase)
13+
- Passes saved state to async handler instead of re-checking after setTimeout
14+
- This ensures manual submit works correctly even when ChatGPT instantly changes UI state
15+
16+
### 🔧 Technical
17+
- `setupManualSubmitListener()` now captures state before ChatGPT processes click
18+
- `handleManualSubmitClick()` now accepts `wasInDictationMode` and `savedBaselineText` parameters
19+
- Added detailed logging for debugging manual submit flow
20+
21+
### 🧪 Testing
22+
- All 140 unit tests passing
23+
- Build successful
24+
25+
---
26+
827
## [0.9.2] - 2026-01-21
928

1029
### 🐛 Bug Fixes

manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"manifest_version": 3,
33
"name": "__MSG_extName__",
44
"short_name": "EchoType",
5-
"version": "0.9.2",
5+
"version": "0.9.3",
66
"description": "__MSG_extDescription__",
77
"author": { "email": "haotool.org@gmail.com" },
88
"homepage_url": "https://github.com/haotool/echotype",

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "echotype",
3-
"version": "0.9.2",
3+
"version": "0.9.3",
44
"description": "Universal voice dictation via ChatGPT Whisper - Chrome Extension",
55
"private": true,
66
"type": "module",

src/content/chatgpt/index.ts

Lines changed: 29 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -167,28 +167,29 @@ chrome.storage.onChanged.addListener((changes, area) => {
167167
* 3. Compute added text (baseline diff)
168168
* 4. Clear the composer
169169
* 5. Send to background for clipboard copy and history
170+
*
171+
* @param wasInDictationMode - Whether dictation was active when click was detected
172+
* @param savedBaselineText - Baseline text captured at click time
170173
*/
171-
async function handleManualSubmitClick(): Promise<void> {
174+
async function handleManualSubmitClick(
175+
wasInDictationMode: boolean,
176+
savedBaselineText: string
177+
): Promise<void> {
172178
if (!manualSubmitAutoCopyEnabled) return;
173179

174-
const status = detectStatus();
175-
// Only capture if we're in recording/listening state
176-
if (status !== 'recording' && status !== 'listening') {
177-
console.log('[EchoType] Manual submit ignored - not in dictation mode:', status);
180+
// Use the saved state from click time, not current state
181+
if (!wasInDictationMode) {
182+
console.log('[EchoType] Manual submit ignored - was not in dictation mode');
178183
return;
179184
}
180185

181186
console.log('[EchoType] Manual submit detected, starting full capture workflow...');
187+
console.log('[EchoType] Baseline text:', savedBaselineText.substring(0, 50));
182188

183-
// Get baseline text from controller state (captured when dictation started)
184-
const controllerState = getControllerState();
185-
const baselineText = controllerState.baselineText || '';
186-
console.log('[EchoType] Baseline text:', baselineText.substring(0, 50));
189+
// Wait for ChatGPT to process the submit and show text in composer
190+
await new Promise(resolve => setTimeout(resolve, 200));
187191

188-
// Wait a moment for the submit click to be processed
189-
await new Promise(resolve => setTimeout(resolve, 100));
190-
191-
// Read pre-submit text (may still be showing waveform)
192+
// Read pre-submit text (may still be showing waveform or transitioning)
192193
const preSubmitText = readComposerText();
193194

194195
// Capture stable text after submit (same as popup workflow)
@@ -198,7 +199,7 @@ async function handleManualSubmitClick(): Promise<void> {
198199
});
199200

200201
const finalText = normalizeText(capture.text);
201-
const addedText = computeAddedText(baselineText, finalText);
202+
const addedText = computeAddedText(savedBaselineText, finalText);
202203

203204
console.log('[EchoType] Manual submit captured:', {
204205
finalText: finalText.substring(0, 50),
@@ -231,6 +232,10 @@ async function handleManualSubmitClick(): Promise<void> {
231232
/**
232233
* Set up click listener for submit button.
233234
* Uses event delegation on document body.
235+
*
236+
* IMPORTANT: We capture the dictation state at click time because
237+
* ChatGPT immediately processes the click and changes the UI state.
238+
* If we check state after setTimeout, it will already be 'idle'.
234239
*/
235240
function setupManualSubmitListener(): void {
236241
document.body.addEventListener('click', (event) => {
@@ -241,9 +246,17 @@ function setupManualSubmitListener(): void {
241246

242247
// Check if clicked element is or is within the submit button
243248
if (submitBtn && (target === submitBtn || submitBtn.contains(target))) {
244-
// Use setTimeout to let the click complete first
249+
// CRITICAL: Capture state NOW, before ChatGPT processes the click
250+
const currentStatus = detectStatus();
251+
const wasInDictationMode = currentStatus === 'recording' || currentStatus === 'listening';
252+
const controllerState = getControllerState();
253+
const savedBaselineText = controllerState.baselineText || '';
254+
255+
console.log('[EchoType] Submit button clicked, status:', currentStatus, 'dictation mode:', wasInDictationMode);
256+
257+
// Use setTimeout to let the click complete first, but pass saved state
245258
setTimeout(() => {
246-
handleManualSubmitClick().catch(console.error);
259+
handleManualSubmitClick(wasInDictationMode, savedBaselineText).catch(console.error);
247260
}, 50);
248261
}
249262
}, true); // Use capture phase to detect before default handlers

0 commit comments

Comments
 (0)