From 33f00a1136c9f7e501b989d432b709d632905304 Mon Sep 17 00:00:00 2001
From: Ariver <shanghai3168@gmail.com>
Date: Sun, 12 Jul 2026 02:09:04 +0800
Subject: [PATCH] chore(mas): initialize split project metadata

---
 C1.source/privatevoice.src/app_live_caption_test.go |  892 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 892 insertions(+), 0 deletions(-)

diff --git a/C1.source/privatevoice.src/app_live_caption_test.go b/C1.source/privatevoice.src/app_live_caption_test.go
index accb99c..1a13c1d 100644
--- a/C1.source/privatevoice.src/app_live_caption_test.go
+++ b/C1.source/privatevoice.src/app_live_caption_test.go
@@ -1,12 +1,19 @@
 package main
 
 import (
+	"context"
+	"errors"
 	"strings"
 	"sync/atomic"
 	"testing"
 	"time"
 
+	"voicesnap/internal/config"
 	"voicesnap/internal/engine"
+	"voicesnap/internal/history"
+	"voicesnap/internal/overlay"
+	"voicesnap/internal/textoutput"
+	"voicesnap/internal/userdict"
 )
 
 func TestLiveCaptionDisplayTextFitsOverlayBuffer(t *testing.T) {
@@ -255,6 +262,760 @@
 	}
 }
 
+func TestHoldPreCaptureAutoConfirmsWithoutAdditionalKeyDown(t *testing.T) {
+	a, cleanup := newHoldPreCaptureTestApp()
+	defer cleanup()
+	a.hk.(*fakeHotkeyListener).down.Store(true)
+
+	a.mu.Lock()
+	a.pollHoldHotkeyLocked(true, true)
+	a.mu.Unlock()
+
+	waitForCondition(t, time.Second, func() bool {
+		a.mu.Lock()
+		defer a.mu.Unlock()
+		return a.isRecording && !a.isHoldRecordingPending && a.hotkeyActive
+	})
+
+	if got := a.recorder.(*fakeAppRecorder).starts.Load(); got != 1 {
+		t.Fatalf("recorder starts = %d, want 1", got)
+	}
+	if got := a.indicator.(*fakeOverlay).lastStatus.Load(); got != string(overlay.StatusRecording) {
+		t.Fatalf("indicator status = %q, want %q", got, overlay.StatusRecording)
+	}
+}
+
+func TestHoldPreCaptureTimerConfirmsWhileRecorderStartBlocked(t *testing.T) {
+	a, cleanup := newHoldPreCaptureTestApp()
+	defer cleanup()
+	a.hk.(*fakeHotkeyListener).down.Store(true)
+	recorder := a.recorder.(*fakeAppRecorder)
+	blockStart := make(chan struct{})
+	recorder.startBlock = blockStart
+
+	a.mu.Lock()
+	a.pollHoldHotkeyLocked(true, true)
+	a.mu.Unlock()
+
+	waitForCondition(t, time.Second, func() bool {
+		return recorder.starts.Load() == 1
+	})
+	waitForCondition(t, time.Second, func() bool {
+		a.mu.Lock()
+		defer a.mu.Unlock()
+		return a.isRecording && !a.isHoldRecordingPending && a.hotkeyActive
+	})
+
+	if got := a.indicator.(*fakeOverlay).lastStatus.Load(); got != string(overlay.StatusRecording) {
+		t.Fatalf("indicator status = %q, want %q while recorder start is blocked", got, overlay.StatusRecording)
+	}
+	close(blockStart)
+	waitForCondition(t, time.Second, func() bool {
+		a.mu.Lock()
+		defer a.mu.Unlock()
+		return !a.isHoldRecorderStarting
+	})
+}
+
+func TestHoldPreCaptureReleaseAfterConfirmRunsRecognitionWhileRecorderStartBlocked(t *testing.T) {
+	a, cleanup := newHoldPreCaptureTestApp()
+	defer cleanup()
+	listener := a.hk.(*fakeHotkeyListener)
+	listener.down.Store(true)
+	recorder := a.recorder.(*fakeAppRecorder)
+	blockStart := make(chan struct{})
+	recorder.startBlock = blockStart
+	recorder.samples = []float32{0.2, 0.1, -0.1, -0.2}
+	recorder.hasVoice.Store(true)
+	eng := &recognizingHoldEngine{text: "PrivateVoice AX POC"}
+	a.replaceEngine(eng)
+	paster := &fakeAppPaster{}
+	a.outputRouter = textoutput.NewRouter(paster)
+
+	a.mu.Lock()
+	a.pollHoldHotkeyLocked(true, true)
+	a.mu.Unlock()
+
+	waitForCondition(t, time.Second, func() bool {
+		a.mu.Lock()
+		defer a.mu.Unlock()
+		return a.isRecording && !a.isHoldRecordingPending && a.isHoldRecorderStarting
+	})
+
+	listener.down.Store(false)
+	a.mu.Lock()
+	a.pollHoldHotkeyLocked(false, true)
+	a.mu.Unlock()
+
+	waitForCondition(t, time.Second, func() bool {
+		return paster.pasted.Load() == "PrivateVoice AX POC"
+	})
+
+	if got := recorder.stopAndGetSamples.Load(); got != 1 {
+		t.Fatalf("StopAndGetSamples while recorder start is blocked = %d, want 1", got)
+	}
+	if got := eng.recognizes.Load(); got != 1 {
+		t.Fatalf("recognizes = %d, want 1", got)
+	}
+	a.mu.Lock()
+	if a.isHoldStopPending {
+		t.Fatal("normal release while recorder is starting should not mark cancel-style pending stop")
+	}
+	a.mu.Unlock()
+
+	close(blockStart)
+	waitForCondition(t, time.Second, func() bool {
+		a.mu.Lock()
+		defer a.mu.Unlock()
+		return !a.isRecording && !a.isHoldRecorderStarting && !a.isHoldStopPending
+	})
+	if got := recorder.stops.Load(); got != 0 {
+		t.Fatalf("app-level fake recorder stops = %d, want 0; native cleanup is covered by audio recorder tests", got)
+	}
+}
+
+func TestHoldPreCaptureReleaseAfterConfirmRunsRecognitionBeforeRecorderStartEntered(t *testing.T) {
+	a, cleanup := newHoldPreCaptureTestApp()
+	defer cleanup()
+	listener := a.hk.(*fakeHotkeyListener)
+	listener.down.Store(true)
+	recorder := a.recorder.(*fakeAppRecorder)
+	preStartBlock := make(chan struct{})
+	recorder.preStartBlock = preStartBlock
+	recorder.samples = []float32{0.2, 0.1, -0.1, -0.2}
+	recorder.hasVoice.Store(true)
+	eng := &recognizingHoldEngine{text: "PrivateVoice AX POC"}
+	a.replaceEngine(eng)
+	paster := &fakeAppPaster{}
+	a.outputRouter = textoutput.NewRouter(paster)
+
+	a.mu.Lock()
+	a.pollHoldHotkeyLocked(true, true)
+	a.mu.Unlock()
+
+	waitForCondition(t, time.Second, func() bool {
+		a.mu.Lock()
+		defer a.mu.Unlock()
+		return a.isRecording && !a.isHoldRecordingPending && a.isHoldRecorderStarting
+	})
+	if got := recorder.starts.Load(); got != 0 {
+		t.Fatalf("recorder Start entered before release = %d, want 0", got)
+	}
+
+	listener.down.Store(false)
+	a.mu.Lock()
+	a.pollHoldHotkeyLocked(false, true)
+	a.mu.Unlock()
+
+	waitForCondition(t, time.Second, func() bool {
+		return paster.pasted.Load() == "PrivateVoice AX POC"
+	})
+	if got := recorder.stopAndGetSamples.Load(); got != 1 {
+		t.Fatalf("StopAndGetSamples before recorder Start entered = %d, want 1", got)
+	}
+	if got := eng.recognizes.Load(); got != 1 {
+		t.Fatalf("recognizes = %d, want 1", got)
+	}
+
+	close(preStartBlock)
+	waitForCondition(t, time.Second, func() bool {
+		a.mu.Lock()
+		defer a.mu.Unlock()
+		return !a.isRecording && !a.isHoldRecorderStarting && !a.isHoldStopPending
+	})
+	if got := recorder.stops.Load(); got != 0 {
+		t.Fatalf("app-level fake recorder stops = %d, want 0 for normal release pipeline", got)
+	}
+	if got := a.indicator.(*fakeOverlay).lastStatus.Load(); got != string(overlay.StatusDone) {
+		t.Fatalf("indicator status = %q, want %q", got, overlay.StatusDone)
+	}
+}
+
+func TestHoldPreCaptureReleaseWaitsForStartupSamplesBeforeRecognition(t *testing.T) {
+	a, cleanup := newHoldPreCaptureTestApp()
+	defer cleanup()
+	listener := a.hk.(*fakeHotkeyListener)
+	listener.down.Store(true)
+	recorder := a.recorder.(*fakeAppRecorder)
+	blockStart := make(chan struct{})
+	readSamplesReady := make(chan struct{})
+	recorder.startBlock = blockStart
+	recorder.readSamplesBlock = readSamplesReady
+	recorder.samples = []float32{0.2, 0.1, -0.1, -0.2}
+	recorder.hasVoice.Store(true)
+	eng := &recognizingHoldEngine{text: "PrivateVoice AX POC"}
+	a.replaceEngine(eng)
+	paster := &fakeAppPaster{}
+	a.outputRouter = textoutput.NewRouter(paster)
+
+	a.mu.Lock()
+	a.pollHoldHotkeyLocked(true, true)
+	a.mu.Unlock()
+
+	waitForCondition(t, time.Second, func() bool {
+		a.mu.Lock()
+		defer a.mu.Unlock()
+		return a.isRecording && !a.isHoldRecordingPending && a.isHoldRecorderStarting
+	})
+
+	listener.down.Store(false)
+	a.mu.Lock()
+	a.pollHoldHotkeyLocked(false, true)
+	a.mu.Unlock()
+
+	time.Sleep(holdStartupSamplePollInterval * 2)
+	if got := recorder.stopAndGetSamples.Load(); got != 0 {
+		t.Fatalf("StopAndGetSamples before startup samples = %d, want 0", got)
+	}
+	if got := eng.recognizes.Load(); got != 0 {
+		t.Fatalf("recognizes before startup samples = %d, want 0", got)
+	}
+
+	close(readSamplesReady)
+	waitForCondition(t, time.Second, func() bool {
+		return paster.pasted.Load() == "PrivateVoice AX POC"
+	})
+	if got := recorder.stopAndGetSamples.Load(); got != 1 {
+		t.Fatalf("StopAndGetSamples after startup samples = %d, want 1", got)
+	}
+	if got := eng.recognizes.Load(); got != 1 {
+		t.Fatalf("recognizes = %d, want 1", got)
+	}
+
+	close(blockStart)
+	waitForCondition(t, time.Second, func() bool {
+		a.mu.Lock()
+		defer a.mu.Unlock()
+		return !a.isRecording && !a.isHoldRecorderStarting && !a.isHoldStopPending
+	})
+}
+
+func TestHoldPreCaptureReleaseWithoutStartupSamplesReportsAudioNotReady(t *testing.T) {
+	oldTimeout := holdStartupSampleWaitTimeout
+	oldPoll := holdStartupSamplePollInterval
+	holdStartupSampleWaitTimeout = 40 * time.Millisecond
+	holdStartupSamplePollInterval = 5 * time.Millisecond
+	defer func() {
+		holdStartupSampleWaitTimeout = oldTimeout
+		holdStartupSamplePollInterval = oldPoll
+	}()
+
+	a, cleanup := newHoldPreCaptureTestApp()
+	defer cleanup()
+	listener := a.hk.(*fakeHotkeyListener)
+	listener.down.Store(true)
+	recorder := a.recorder.(*fakeAppRecorder)
+	blockStart := make(chan struct{})
+	recorder.startBlock = blockStart
+	eng := &recognizingHoldEngine{text: "PrivateVoice AX POC"}
+	a.replaceEngine(eng)
+	paster := &fakeAppPaster{}
+	a.outputRouter = textoutput.NewRouter(paster)
+
+	a.mu.Lock()
+	a.pollHoldHotkeyLocked(true, true)
+	a.mu.Unlock()
+
+	waitForCondition(t, time.Second, func() bool {
+		a.mu.Lock()
+		defer a.mu.Unlock()
+		return a.isRecording && !a.isHoldRecordingPending && a.isHoldRecorderStarting
+	})
+
+	listener.down.Store(false)
+	a.mu.Lock()
+	a.pollHoldHotkeyLocked(false, true)
+	a.mu.Unlock()
+
+	waitForCondition(t, time.Second, func() bool {
+		return recorder.stops.Load() == 1 &&
+			a.indicator.(*fakeOverlay).lastStatus.Load() == string(overlay.StatusError)
+	})
+	if got := recorder.stopAndGetSamples.Load(); got != 0 {
+		t.Fatalf("StopAndGetSamples after audio-not-ready timeout = %d, want 0", got)
+	}
+	if got := eng.recognizes.Load(); got != 0 {
+		t.Fatalf("recognizes after audio-not-ready timeout = %d, want 0", got)
+	}
+	if got := paster.pasted.Load(); got != nil {
+		t.Fatalf("pasted text after audio-not-ready timeout = %v, want nil", got)
+	}
+	if got := a.indicator.(*fakeOverlay).lastStatus.Load(); got != string(overlay.StatusError) {
+		t.Fatalf("indicator status = %q, want %q", got, overlay.StatusError)
+	}
+
+	close(blockStart)
+	waitForCondition(t, time.Second, func() bool {
+		a.mu.Lock()
+		defer a.mu.Unlock()
+		return !a.isRecording && !a.isHoldRecorderStarting && !a.isHoldStopPending
+	})
+}
+
+func TestHoldPreCaptureReleaseWhileRecorderPermissionPendingDoesNotRecognize(t *testing.T) {
+	oldTimeout := holdStartupSampleWaitTimeout
+	oldPoll := holdStartupSamplePollInterval
+	holdStartupSampleWaitTimeout = 40 * time.Millisecond
+	holdStartupSamplePollInterval = 5 * time.Millisecond
+	defer func() {
+		holdStartupSampleWaitTimeout = oldTimeout
+		holdStartupSamplePollInterval = oldPoll
+	}()
+
+	a, cleanup := newHoldPreCaptureTestApp()
+	defer cleanup()
+	listener := a.hk.(*fakeHotkeyListener)
+	listener.down.Store(true)
+	recorder := a.recorder.(*fakeAppRecorder)
+	preStartBlock := make(chan struct{})
+	recorder.preStartBlock = preStartBlock
+	eng := &recognizingHoldEngine{text: "PrivateVoice AX POC"}
+	a.replaceEngine(eng)
+	paster := &fakeAppPaster{}
+	a.outputRouter = textoutput.NewRouter(paster)
+
+	a.mu.Lock()
+	a.pollHoldHotkeyLocked(true, true)
+	a.mu.Unlock()
+
+	waitForCondition(t, time.Second, func() bool {
+		a.mu.Lock()
+		defer a.mu.Unlock()
+		return a.isRecording && !a.isHoldRecordingPending && a.isHoldRecorderStarting
+	})
+	if got := recorder.starts.Load(); got != 0 {
+		t.Fatalf("recorder Start entered before permission pending release = %d, want 0", got)
+	}
+
+	listener.down.Store(false)
+	a.mu.Lock()
+	a.pollHoldHotkeyLocked(false, true)
+	a.mu.Unlock()
+
+	waitForCondition(t, time.Second, func() bool {
+		return recorder.stops.Load() == 1 &&
+			a.indicator.(*fakeOverlay).lastStatus.Load() == string(overlay.StatusError)
+	})
+	if got := recorder.stopAndGetSamples.Load(); got != 0 {
+		t.Fatalf("StopAndGetSamples after permission-pending audio-not-ready = %d, want 0", got)
+	}
+	if got := eng.recognizes.Load(); got != 0 {
+		t.Fatalf("recognizes after permission-pending audio-not-ready = %d, want 0", got)
+	}
+	if got := paster.pasted.Load(); got != nil {
+		t.Fatalf("pasted text after permission-pending audio-not-ready = %v, want nil", got)
+	}
+
+	close(preStartBlock)
+	waitForCondition(t, time.Second, func() bool {
+		a.mu.Lock()
+		defer a.mu.Unlock()
+		return !a.isRecording && !a.isHoldRecorderStarting && !a.isHoldStopPending
+	})
+	if got := recorder.starts.Load(); got != 1 {
+		t.Fatalf("recorder Start after permission resumes = %d, want 1", got)
+	}
+	if got := recorder.stops.Load(); got != 1 {
+		t.Fatalf("recorder stops after late permission success = %d, want 1", got)
+	}
+}
+
+func TestHoldPreCaptureEscapeAfterConfirmDefersStopUntilRecorderStart(t *testing.T) {
+	a, cleanup := newHoldPreCaptureTestApp()
+	defer cleanup()
+	listener := a.hk.(*fakeHotkeyListener)
+	listener.down.Store(true)
+	recorder := a.recorder.(*fakeAppRecorder)
+	blockStart := make(chan struct{})
+	recorder.startBlock = blockStart
+	captionCancelled := false
+
+	a.mu.Lock()
+	a.pollHoldHotkeyLocked(true, true)
+	a.mu.Unlock()
+
+	waitForCondition(t, time.Second, func() bool {
+		a.mu.Lock()
+		defer a.mu.Unlock()
+		return a.isRecording && !a.isHoldRecordingPending && a.isHoldRecorderStarting
+	})
+
+	a.mu.Lock()
+	a.liveCaptionCancel = func() { captionCancelled = true }
+	a.mu.Unlock()
+	listener.escapeDown.Store(true)
+
+	done := make(chan struct{})
+	go func() {
+		a.pollHotkey()
+		close(done)
+	}()
+	select {
+	case <-done:
+	case <-time.After(100 * time.Millisecond):
+		t.Fatal("Escape cancel should not wait for blocked recorder start")
+	}
+
+	if got := recorder.stops.Load(); got != 1 {
+		t.Fatalf("recorder stops before start returned = %d, want 1", got)
+	}
+	if got := recorder.stopAndGetSamples.Load(); got != 0 {
+		t.Fatalf("StopAndGetSamples before recorder start returned = %d, want 0", got)
+	}
+	a.mu.Lock()
+	if !a.isHoldStopPending {
+		t.Fatal("Escape while recorder is starting should mark pending stop")
+	}
+	if a.liveCaptionCancel != nil {
+		t.Fatal("Escape while recorder is starting should clear live caption")
+	}
+	a.mu.Unlock()
+	if !captionCancelled {
+		t.Fatal("Escape while recorder is starting should cancel live caption")
+	}
+
+	close(blockStart)
+	waitForCondition(t, time.Second, func() bool {
+		a.mu.Lock()
+		defer a.mu.Unlock()
+		return !a.isRecording && !a.isHoldRecorderStarting && !a.isHoldStopPending
+	})
+
+	if got := recorder.stopAndGetSamples.Load(); got != 0 {
+		t.Fatalf("StopAndGetSamples after deferred Escape stop = %d, want 0", got)
+	}
+	if got := recorder.stops.Load(); got != 1 {
+		t.Fatalf("recorder stops = %d, want 1", got)
+	}
+	if got := a.indicator.(*fakeOverlay).lastStatus.Load(); got != string(overlay.StatusCancelled) {
+		t.Fatalf("indicator status = %q, want %q", got, overlay.StatusCancelled)
+	}
+}
+
+func TestPollHotkeyIgnoresUnsetHotkey(t *testing.T) {
+	a, cleanup := newHoldPreCaptureTestApp()
+	defer cleanup()
+	listener := a.hk.(*fakeHotkeyListener)
+	listener.down.Store(true)
+	a.cfg.HotkeyVK = config.UnsetHotkeyVK
+
+	a.pollHotkey()
+
+	if got := listener.keyChecks.Load(); got != 0 {
+		t.Fatalf("IsKeyDown calls for unset hotkey = %d, want 0", got)
+	}
+	if a.hotkeyActive {
+		t.Fatal("unset hotkey must not become active")
+	}
+	if a.isRecording || a.isFreetalking {
+		t.Fatal("unset hotkey must not start recording")
+	}
+}
+
+func TestHoldPreCaptureEscapeWhileRecorderPermissionPendingStopsLateStart(t *testing.T) {
+	a, cleanup := newHoldPreCaptureTestApp()
+	defer cleanup()
+	listener := a.hk.(*fakeHotkeyListener)
+	listener.down.Store(true)
+	recorder := a.recorder.(*fakeAppRecorder)
+	preStartBlock := make(chan struct{})
+	recorder.preStartBlock = preStartBlock
+	eng := &recognizingHoldEngine{text: "PrivateVoice AX POC"}
+	a.replaceEngine(eng)
+
+	a.mu.Lock()
+	a.pollHoldHotkeyLocked(true, true)
+	a.mu.Unlock()
+
+	waitForCondition(t, time.Second, func() bool {
+		a.mu.Lock()
+		defer a.mu.Unlock()
+		return a.isRecording && !a.isHoldRecordingPending && a.isHoldRecorderStarting
+	})
+	if got := recorder.starts.Load(); got != 0 {
+		t.Fatalf("recorder Start entered before Escape during permission pending = %d, want 0", got)
+	}
+
+	listener.escapeDown.Store(true)
+	done := make(chan struct{})
+	go func() {
+		a.pollHotkey()
+		close(done)
+	}()
+	select {
+	case <-done:
+	case <-time.After(100 * time.Millisecond):
+		t.Fatal("Escape during permission pending should not wait for recorder Start")
+	}
+	if got := recorder.stops.Load(); got != 1 {
+		t.Fatalf("recorder Stop during permission-pending Escape = %d, want 1", got)
+	}
+	if got := recorder.stopAndGetSamples.Load(); got != 0 {
+		t.Fatalf("StopAndGetSamples during permission-pending Escape = %d, want 0", got)
+	}
+	if got := eng.recognizes.Load(); got != 0 {
+		t.Fatalf("recognizes during permission-pending Escape = %d, want 0", got)
+	}
+
+	close(preStartBlock)
+	waitForCondition(t, time.Second, func() bool {
+		a.mu.Lock()
+		defer a.mu.Unlock()
+		return !a.isRecording && !a.isHoldRecorderStarting && !a.isHoldStopPending
+	})
+	if got := recorder.stops.Load(); got != 1 {
+		t.Fatalf("recorder stops after late permission success = %d, want 1", got)
+	}
+	if got := a.indicator.(*fakeOverlay).lastStatus.Load(); got != string(overlay.StatusCancelled) {
+		t.Fatalf("indicator status = %q, want %q", got, overlay.StatusCancelled)
+	}
+}
+
+func TestHoldPreCaptureCombinationWhileRecorderPermissionPendingStopsLateStart(t *testing.T) {
+	a, cleanup := newHoldPreCaptureTestApp()
+	defer cleanup()
+	listener := a.hk.(*fakeHotkeyListener)
+	listener.down.Store(true)
+	recorder := a.recorder.(*fakeAppRecorder)
+	preStartBlock := make(chan struct{})
+	recorder.preStartBlock = preStartBlock
+	eng := &recognizingHoldEngine{text: "PrivateVoice AX POC"}
+	a.replaceEngine(eng)
+
+	a.mu.Lock()
+	a.pollHoldHotkeyLocked(true, true)
+	a.mu.Unlock()
+	listener.otherSince.Store(true)
+
+	waitForCondition(t, time.Second, func() bool {
+		a.mu.Lock()
+		defer a.mu.Unlock()
+		return a.isCombination && !a.isRecording && !a.isHoldRecordingPending && a.isHoldStopPending
+	})
+	if got := recorder.stops.Load(); got != 1 {
+		t.Fatalf("recorder Stop during permission-pending combination cancel = %d, want 1", got)
+	}
+	if got := recorder.stopAndGetSamples.Load(); got != 0 {
+		t.Fatalf("StopAndGetSamples during permission-pending combination cancel = %d, want 0", got)
+	}
+	if got := eng.recognizes.Load(); got != 0 {
+		t.Fatalf("recognizes during permission-pending combination cancel = %d, want 0", got)
+	}
+
+	close(preStartBlock)
+	waitForCondition(t, time.Second, func() bool {
+		a.mu.Lock()
+		defer a.mu.Unlock()
+		return !a.isRecording && !a.isHoldRecorderStarting && !a.isHoldStopPending
+	})
+	if got := recorder.stops.Load(); got != 1 {
+		t.Fatalf("recorder stops after late combination permission success = %d, want 1", got)
+	}
+}
+
+func TestHoldPreCaptureReleaseAfterRecorderStartRunsRecognitionAndOutput(t *testing.T) {
+	a, cleanup := newHoldPreCaptureTestApp()
+	defer cleanup()
+	listener := a.hk.(*fakeHotkeyListener)
+	listener.down.Store(true)
+	recorder := a.recorder.(*fakeAppRecorder)
+	recorder.samples = []float32{0.2, 0.1, -0.1, -0.2}
+	recorder.hasVoice.Store(true)
+	eng := &recognizingHoldEngine{text: "PrivateVoice AX POC"}
+	a.replaceEngine(eng)
+	paster := &fakeAppPaster{}
+	a.outputRouter = textoutput.NewRouter(paster)
+
+	a.mu.Lock()
+	a.pollHoldHotkeyLocked(true, true)
+	a.mu.Unlock()
+
+	waitForCondition(t, time.Second, func() bool {
+		a.mu.Lock()
+		defer a.mu.Unlock()
+		return a.isRecording && !a.isHoldRecordingPending && !a.isHoldRecorderStarting
+	})
+
+	listener.down.Store(false)
+	a.mu.Lock()
+	a.pollHoldHotkeyLocked(false, true)
+	a.mu.Unlock()
+
+	waitForCondition(t, time.Second, func() bool {
+		return paster.pasted.Load() == "PrivateVoice AX POC"
+	})
+
+	if got := recorder.stopAndGetSamples.Load(); got != 1 {
+		t.Fatalf("StopAndGetSamples = %d, want 1", got)
+	}
+	if got := eng.recognizes.Load(); got != 1 {
+		t.Fatalf("recognizes = %d, want 1", got)
+	}
+	if got := recorder.stops.Load(); got != 0 {
+		t.Fatalf("recorder Stop = %d, want 0 for normal recognition path", got)
+	}
+}
+
+func TestHoldPreCaptureReleasePipelineSuppressesLateRecorderStartError(t *testing.T) {
+	a, cleanup := newHoldPreCaptureTestApp()
+	defer cleanup()
+	listener := a.hk.(*fakeHotkeyListener)
+	listener.down.Store(true)
+	recorder := a.recorder.(*fakeAppRecorder)
+	blockStart := make(chan struct{})
+	recorder.startBlock = blockStart
+	recorder.startErr = errors.New("start failed")
+	recorder.samples = []float32{0.2, 0.1, -0.1, -0.2}
+	recorder.hasVoice.Store(true)
+	eng := &recognizingHoldEngine{text: "PrivateVoice AX POC"}
+	a.replaceEngine(eng)
+	paster := &fakeAppPaster{}
+	a.outputRouter = textoutput.NewRouter(paster)
+
+	a.mu.Lock()
+	a.pollHoldHotkeyLocked(true, true)
+	a.mu.Unlock()
+
+	waitForCondition(t, time.Second, func() bool {
+		a.mu.Lock()
+		defer a.mu.Unlock()
+		return a.isRecording && !a.isHoldRecordingPending && a.isHoldRecorderStarting
+	})
+
+	listener.down.Store(false)
+	a.mu.Lock()
+	a.pollHoldHotkeyLocked(false, true)
+	a.mu.Unlock()
+
+	waitForCondition(t, time.Second, func() bool {
+		return paster.pasted.Load() == "PrivateVoice AX POC"
+	})
+	close(blockStart)
+	waitForCondition(t, time.Second, func() bool {
+		a.mu.Lock()
+		defer a.mu.Unlock()
+		return !a.isHoldRecorderStarting
+	})
+
+	if got := eng.recognizes.Load(); got != 1 {
+		t.Fatalf("recognizes = %d, want 1", got)
+	}
+	if got := a.indicator.(*fakeOverlay).lastStatus.Load(); got != string(overlay.StatusDone) {
+		t.Fatalf("late recorder start error overwrote indicator status = %q, want %q", got, overlay.StatusDone)
+	}
+}
+
+func TestHoldPreCaptureRecorderStartLateErrorCleansConfirmedState(t *testing.T) {
+	a, cleanup := newHoldPreCaptureTestApp()
+	defer cleanup()
+	a.hk.(*fakeHotkeyListener).down.Store(true)
+	recorder := a.recorder.(*fakeAppRecorder)
+	blockStart := make(chan struct{})
+	recorder.startBlock = blockStart
+	recorder.startErr = errors.New("start failed")
+	captionCancelled := false
+	a.liveCaptionCancel = func() { captionCancelled = true }
+
+	a.mu.Lock()
+	a.pollHoldHotkeyLocked(true, true)
+	a.mu.Unlock()
+
+	waitForCondition(t, time.Second, func() bool {
+		a.mu.Lock()
+		defer a.mu.Unlock()
+		return a.isRecording && !a.isHoldRecordingPending && a.isHoldRecorderStarting && a.liveCaptionCancel != nil
+	})
+
+	close(blockStart)
+	waitForCondition(t, time.Second, func() bool {
+		a.mu.Lock()
+		defer a.mu.Unlock()
+		return !a.isRecording && !a.isHoldRecorderStarting && a.liveCaptionCancel == nil
+	})
+
+	if !captionCancelled {
+		t.Fatal("late recorder start error should cancel live caption")
+	}
+	if got := a.indicator.(*fakeOverlay).lastStatus.Load(); got != string(overlay.StatusError) {
+		t.Fatalf("indicator status = %q, want %q", got, overlay.StatusError)
+	}
+}
+
+func TestHoldPreCaptureReleaseBeforeActivationCancelsTimer(t *testing.T) {
+	a, cleanup := newHoldPreCaptureTestApp()
+	defer cleanup()
+	a.hk.(*fakeHotkeyListener).down.Store(true)
+
+	a.mu.Lock()
+	a.pollHoldHotkeyLocked(true, true)
+	a.hk.(*fakeHotkeyListener).down.Store(false)
+	a.pollHoldHotkeyLocked(false, true)
+	a.mu.Unlock()
+
+	time.Sleep(holdActivationDelay + 50*time.Millisecond)
+
+	a.mu.Lock()
+	defer a.mu.Unlock()
+	if a.isRecording || a.isHoldRecordingPending {
+		t.Fatalf("recording=%t pending=%t, want both false after early release", a.isRecording, a.isHoldRecordingPending)
+	}
+	if got := a.recorder.(*fakeAppRecorder).stops.Load(); got != 1 {
+		t.Fatalf("recorder stops = %d, want 1", got)
+	}
+}
+
+func TestHoldPreCaptureTimerCancelsWhenKeyReleasedBeforePoll(t *testing.T) {
+	a, cleanup := newHoldPreCaptureTestApp()
+	defer cleanup()
+	a.hk.(*fakeHotkeyListener).down.Store(true)
+
+	a.mu.Lock()
+	a.pollHoldHotkeyLocked(true, true)
+	a.mu.Unlock()
+
+	a.hk.(*fakeHotkeyListener).down.Store(false)
+
+	waitForCondition(t, time.Second, func() bool {
+		a.mu.Lock()
+		defer a.mu.Unlock()
+		return !a.isRecording && !a.isHoldRecordingPending
+	})
+
+	a.mu.Lock()
+	defer a.mu.Unlock()
+	if a.hotkeyActive != true {
+		t.Fatal("hotkeyActive should remain true until release poll handles the key-up")
+	}
+	if got := a.recorder.(*fakeAppRecorder).stops.Load(); got != 1 {
+		t.Fatalf("recorder stops = %d, want 1", got)
+	}
+	if got := a.indicator.(*fakeOverlay).lastStatus.Load(); got == string(overlay.StatusRecording) {
+		t.Fatalf("indicator status = %q, should not confirm recording after physical release", got)
+	}
+}
+
+func TestHoldPreCaptureCombinationBeforeActivationCancels(t *testing.T) {
+	a, cleanup := newHoldPreCaptureTestApp()
+	defer cleanup()
+	a.hk.(*fakeHotkeyListener).down.Store(true)
+
+	a.mu.Lock()
+	a.pollHoldHotkeyLocked(true, true)
+	a.mu.Unlock()
+
+	a.hk.(*fakeHotkeyListener).otherSince.Store(true)
+
+	waitForCondition(t, time.Second, func() bool {
+		a.mu.Lock()
+		defer a.mu.Unlock()
+		return a.isCombination && !a.isRecording && !a.isHoldRecordingPending
+	})
+
+	if got := a.recorder.(*fakeAppRecorder).stops.Load(); got != 1 {
+		t.Fatalf("recorder stops = %d, want 1", got)
+	}
+}
+
 func TestStartRecordingIgnoredWhileStoppingRecording(t *testing.T) {
 	a := &App{isStoppingRecording: true}
 
@@ -307,3 +1068,134 @@
 func (e holdPreCaptureTestEngine) HoldPreCaptureEnabled() bool {
 	return e.enabled
 }
+
+func newHoldPreCaptureTestApp() (*App, context.CancelFunc) {
+	ctx, cancel := context.WithCancel(context.Background())
+	a := &App{
+		ctx:       ctx,
+		cancel:    cancel,
+		cfg:       &config.Config{HotkeyVK: 0x5C, HotkeyMode: config.HotkeyModeHold},
+		recorder:  &fakeAppRecorder{},
+		hk:        &fakeHotkeyListener{},
+		indicator: &fakeOverlay{},
+		history:   history.New(),
+		userdict:  userdict.New(),
+	}
+	a.replaceEngine(holdPreCaptureTestEngine{enabled: true})
+	a.holdPreCaptureEnabled.Store(true)
+	return a, cancel
+}
+
+type fakeAppRecorder struct {
+	starts            atomic.Int32
+	stops             atomic.Int32
+	stopAndGetSamples atomic.Int32
+	hasVoice          atomic.Bool
+	samples           []float32
+	preStartBlock     <-chan struct{}
+	startBlock        <-chan struct{}
+	readSamplesBlock  <-chan struct{}
+	startErr          error
+}
+
+func (r *fakeAppRecorder) OnVolume(func(float64))      {}
+func (r *fakeAppRecorder) OnDeviceChange(func(string)) {}
+func (r *fakeAppRecorder) Start() error {
+	if r.preStartBlock != nil {
+		<-r.preStartBlock
+	}
+	r.starts.Add(1)
+	if r.startBlock != nil {
+		<-r.startBlock
+	}
+	return r.startErr
+}
+func (r *fakeAppRecorder) Stop() { r.stops.Add(1) }
+func (r *fakeAppRecorder) StopAndGetSamples() []float32 {
+	r.stopAndGetSamples.Add(1)
+	return append([]float32(nil), r.samples...)
+}
+func (r *fakeAppRecorder) HasVoiceActivity() bool { return r.hasVoice.Load() }
+func (r *fakeAppRecorder) ReadSamplesSince(offset int) ([]float32, int) {
+	if r.readSamplesBlock != nil {
+		select {
+		case <-r.readSamplesBlock:
+		default:
+			return nil, 0
+		}
+	}
+	total := len(r.samples)
+	if offset < 0 {
+		offset = 0
+	}
+	if offset > total {
+		offset = total
+	}
+	return append([]float32(nil), r.samples[offset:]...), total
+}
+func (r *fakeAppRecorder) Close() {}
+
+type recognizingHoldEngine struct {
+	text       string
+	recognizes atomic.Int32
+}
+
+func (e *recognizingHoldEngine) Recognize([]float32) (string, error) {
+	e.recognizes.Add(1)
+	return e.text, nil
+}
+func (e *recognizingHoldEngine) HardwareInfo() string        { return "test" }
+func (e *recognizingHoldEngine) Close()                      {}
+func (e *recognizingHoldEngine) HoldPreCaptureEnabled() bool { return true }
+
+type fakeAppPaster struct {
+	pasted atomic.Value
+	typed  atomic.Value
+}
+
+func (p *fakeAppPaster) Paste(text string, keepClipboard bool) error {
+	p.pasted.Store(text)
+	return nil
+}
+
+func (p *fakeAppPaster) TypeText(text string) error {
+	p.typed.Store(text)
+	return nil
+}
+
+type fakeHotkeyListener struct {
+	down       atomic.Bool
+	escapeDown atomic.Bool
+	otherSince atomic.Bool
+	keyChecks  atomic.Int32
+}
+
+func (h *fakeHotkeyListener) IsKeyDown(key int) bool {
+	h.keyChecks.Add(1)
+	if key == 0x1B {
+		return h.escapeDown.Load()
+	}
+	return h.down.Load()
+}
+func (h *fakeHotkeyListener) IsAnyOtherKeyPressed(int) bool { return false }
+func (h *fakeHotkeyListener) IsAnyOtherKeyPressedSince(int, time.Time) bool {
+	return h.otherSince.Load()
+}
+func (h *fakeHotkeyListener) Close() {}
+
+type fakeOverlay struct {
+	lastStatus atomic.Value
+}
+
+func (o *fakeOverlay) Show() {}
+func (o *fakeOverlay) Hide() {}
+func (o *fakeOverlay) SetStatus(status overlay.Status, text string) {
+	o.lastStatus.Store(string(status))
+}
+func (o *fakeOverlay) SetVolume(float64)        {}
+func (o *fakeOverlay) SetPosition(int, int)     {}
+func (o *fakeOverlay) GetPosition() (int, int)  { return 0, 0 }
+func (o *fakeOverlay) Size() (int, int)         { return 170, 48 }
+func (o *fakeOverlay) OnDragged(func(int, int)) {}
+func (o *fakeOverlay) Close()                   {}
+func (o *fakeOverlay) AutoPosition()            {}

--
Gitblit v1.9.3