Skip to content

feat(gui): wire partial/final transcript commands to STT pipeline seam#386

Merged
Coldaine merged 3 commits intomainfrom
worker/coldvox/w-a8df-3c-partial-transcript
Apr 3, 2026
Merged

feat(gui): wire partial/final transcript commands to STT pipeline seam#386
Coldaine merged 3 commits intomainfrom
worker/coldvox/w-a8df-3c-partial-transcript

Conversation

@Coldaine
Copy link
Copy Markdown
Owner

@Coldaine Coldaine commented Apr 2, 2026

What Changed

Phase 3B: Replace the demo seam with real runtime bindings from the existing Rust pipeline crates.

Rust backend (lib.rs + state.rs)

Added 5 new Tauri commands that map directly onto the STT pipeline lifecycle:

Command Behavior
Feeds live partial text; shell stays in Listening
Commits partial→final; transitions to Ready
Transition to Processing (STT finalizing utterance)
Transition to Listening (new utterance started)
Returns to Idle; clears all transcript state

Corresponding methods added: , , , , . All have unit tests.

React bridge (overlayBridge.ts)

Exported 5 new bridge helpers mirroring the Tauri commands. is the hot path called on every STT partial.

Hook (useOverlayShell.ts)

— debounces rapid partials with an 80ms flush timer to avoid flooding the shell on fast STT output. Returns a for cleanup on unmount.

Returns the new pipeline wiring functions alongside the existing demo commands:

Validation

  • in — all 4 tests pass (2 hook tests + 2 component tests)
  • Demo driver fully preserved for UX shell testing
  • drift reverted to keep the diff clean

Context

Follows the plan in Phase 3B action items:

  • ✅ Replace demo seam with real runtime bindings
  • ✅ Implement live partial transcript updates from the actual audio/STT path

Next step: Connect the actual coldvox-audio / coldvox-stt event streams to these commands from the Rust host shell (not in this tranche — that requires deeper pipeline integration).


Task: w-2b39

Phase 3B: Replace demo seam with real runtime bindings.

- Add 5 new Tauri commands: update_partial_transcript,
  update_final_transcript, set_overlay_processing,
  set_overlay_listening, stop_overlay_capture
- State methods: apply_partial_transcript, apply_final_transcript,
  apply_processing_state, apply_listening_state, stop_capture
- React bridge helpers with 80ms debounce on partials
- Unit tests for all new state transitions
- Demo driver preserved for UX shell testing
Copilot AI review requested due to automatic review settings April 2, 2026 03:46
@qodo-code-review
Copy link
Copy Markdown

Review Summary by Qodo

Wire STT pipeline commands to overlay shell with debounced partial updates

✨ Enhancement

Grey Divider

Walkthroughs

Description
• Add 5 new Tauri commands for STT pipeline integration (partial/final transcript,
  processing/listening state transitions, capture stop)
• Implement corresponding state methods with full transcript lifecycle management
• Add 80ms debounce mechanism in React hook to prevent flooding UI with rapid partial updates
• Include comprehensive unit tests for all new state transitions and cleanup logic
Diagram
flowchart LR
  STT["STT Pipeline"] -->|"update_partial_transcript"| Partial["Partial Transcript<br/>Listening State"]
  STT -->|"update_final_transcript"| Final["Final Transcript<br/>Ready State"]
  STT -->|"set_overlay_processing"| Processing["Processing State"]
  STT -->|"set_overlay_listening"| Listening["Listening State"]
  STT -->|"stop_overlay_capture"| Idle["Idle State<br/>Cleared"]
  Partial -->|"80ms debounce"| Hook["useOverlayShell Hook"]
  Hook -->|"Bridge"| React["React UI"]
Loading

Grey Divider

File Changes

1. crates/coldvox-gui/src-tauri/src/lib.rs ✨ Enhancement +76/-0

Add STT pipeline Tauri command handlers

• Added 5 new Tauri command handlers: update_partial_transcript, update_final_transcript,
 set_overlay_processing, set_overlay_listening, stop_overlay_capture
• Each command invokes corresponding state model method and emits snapshot with appropriate event
 name
• Registered all 5 commands in the invoke handler for IPC exposure

crates/coldvox-gui/src-tauri/src/lib.rs


2. crates/coldvox-gui/src-tauri/src/state.rs ✨ Enhancement +122/-0

Implement STT pipeline state transitions

• Implemented 5 state transition methods: apply_partial_transcript, apply_final_transcript,
 apply_processing_state, apply_listening_state, stop_capture
• Each method updates snapshot status, transcript fields, and status detail appropriately
• Added 3 new unit tests covering partial→final promotion, listening state maintenance, and capture
 cleanup

crates/coldvox-gui/src-tauri/src/state.rs


3. crates/coldvox-gui/src/lib/overlayBridge.ts ✨ Enhancement +25/-0

Export STT pipeline bridge functions

• Exported 5 new bridge functions mirroring Tauri commands for React consumption
• Functions invoke corresponding backend commands and return OverlaySnapshot promises
• Maintains consistent naming and typing with existing bridge pattern

crates/coldvox-gui/src/lib/overlayBridge.ts


View more (2)
4. crates/coldvox-gui/src/hooks/useOverlayShell.ts ✨ Enhancement +55/-1

Add debounced partial transcript queueing

• Added debounce mechanism with 80ms flush timer to batch rapid partial transcript updates
• Implemented queuePartialTranscript for debounced updates and cancelPendingPartial for cleanup
• Added cleanup effect to cancel pending flushes on unmount
• Exported 5 new pipeline wiring functions alongside existing demo commands

crates/coldvox-gui/src/hooks/useOverlayShell.ts


5. crates/coldvox-gui/src/hooks/useOverlayShell.test.tsx 🧪 Tests +29/-0

Add test mocks for STT pipeline functions

• Added mock implementations for all 5 new bridge functions with appropriate snapshot responses
• Mocks return correct status states (listening, ready, processing, idle) matching backend behavior
• Registered mocks in vi.mock call for proper test isolation

crates/coldvox-gui/src/hooks/useOverlayShell.test.tsx


Grey Divider

Qodo Logo

@qodo-code-review
Copy link
Copy Markdown

qodo-code-review bot commented Apr 2, 2026

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (2) 📎 Requirement gaps (0) 🎨 UX Issues (0)

Grey Divider


Action required

1. Demo keeps running after stop🐞 Bug ≡ Correctness
Description
OverlayModel::stop_capture() does not increment demo_token, so a currently-running demo driver
thread will keep emitting snapshots even after stop_overlay_capture sets the overlay back to Idle.
This can overwrite the stopped (Idle) snapshot with subsequent demo snapshots, making Stop
ineffective and corrupting state.
Code

crates/coldvox-gui/src-tauri/src/state.rs[R185-196]

+    /// Stop capture and return to Idle, clearing all transcript state.
+    /// Unlike `stop()` which increments the demo token, this is used by the real pipeline.
+    pub fn stop_capture(&mut self) -> OverlaySnapshot {
+        self.snapshot.status = OverlayStatus::Idle;
+        self.snapshot.paused = false;
+        self.snapshot.partial_transcript.clear();
+        self.snapshot.final_transcript.clear();
+        self.snapshot.status_detail =
+            "Capture stopped. The seam is ready for the next session.".to_string();
+        self.snapshot.error_message = None;
+        self.snapshot()
+    }
Evidence
The demo driver thread exits only when current_demo_token() differs from the token it was started
with. stop() and clear() bump demo_token, but the newly-added stop_capture() intentionally
does not, so it will not stop an in-flight demo driver; the new Tauri command stop_overlay_capture
calls this method.

crates/coldvox-gui/src-tauri/src/state.rs[74-92]
crates/coldvox-gui/src-tauri/src/state.rs[185-196]
crates/coldvox-gui/src-tauri/src/lib.rs[198-207]
crates/coldvox-gui/src-tauri/src/lib.rs[209-229]
crates/coldvox-gui/src-tauri/src/lib.rs[232-255]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`OverlayModel::stop_capture()` resets the snapshot to Idle but does not increment `demo_token`. If the demo driver thread is running, it will continue to emit demo snapshots (token unchanged) and overwrite the Idle snapshot returned by `stop_overlay_capture`.
### Issue Context
The demo driver checks `current_demo_token()` against the captured token to decide whether to stop. `stop()` and `clear()` increment the token, but `stop_capture()` currently does not.
### Fix Focus Areas
- crates/coldvox-gui/src-tauri/src/state.rs[185-196]
- crates/coldvox-gui/src-tauri/src/lib.rs[198-207]
- crates/coldvox-gui/src-tauri/src/lib.rs[209-255]
### Suggested fix
Increment `self.demo_token` inside `stop_capture()` (or otherwise invalidate the demo driver) so that calling `stop_overlay_capture` reliably stops any in-flight demo thread before returning Idle. If you truly need “real pipeline stop without affecting demo”, consider separating demo state from pipeline state rather than sharing a single token and model.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. Missing CHANGELOG for STT commands 📘 Rule violation ⚙ Maintainability
Description
This PR introduces new user-visible STT/overlay lifecycle commands and UI wiring but does not update
CHANGELOG.md. Users and release notes will miss these feature additions.
Code

crates/coldvox-gui/src/lib/overlayBridge.ts[R47-70]

+/// Feed a live partial transcript update from the STT pipeline.
+export function updatePartialTranscript(text: string): Promise<OverlaySnapshot> {
+  return invoke<OverlaySnapshot>("update_partial_transcript", { text });
+}
+
+/// Feed a final transcript from the STT pipeline.
+export function updateFinalTranscript(text: string): Promise<OverlaySnapshot> {
+  return invoke<OverlaySnapshot>("update_final_transcript", { text });
+}
+
+/// Transition the overlay to Processing state.
+export function setOverlayProcessing(): Promise<OverlaySnapshot> {
+  return invoke<OverlaySnapshot>("set_overlay_processing");
+}
+
+/// Transition the overlay to Listening state.
+export function setOverlayListening(): Promise<OverlaySnapshot> {
+  return invoke<OverlaySnapshot>("set_overlay_listening");
+}
+
+/// Stop real capture and return to Idle.
+export function stopOverlayCapture(): Promise<OverlaySnapshot> {
+  return invoke<OverlaySnapshot>("stop_overlay_capture");
+}
Evidence
PR Compliance ID 8 requires CHANGELOG.md updates for user-visible changes; this PR adds new STT
pipeline bridge APIs (updatePartialTranscript, updateFinalTranscript, setOverlayProcessing,
setOverlayListening, stopOverlayCapture) that change GUI capabilities.

AGENTS.md
crates/coldvox-gui/src/lib/overlayBridge.ts[47-70]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
This PR adds new user-visible STT/overlay lifecycle commands and wiring, but `CHANGELOG.md` was not updated to reflect the change.
## Issue Context
The new bridge APIs expose runtime behavior changes for partial/final transcript updates and overlay state transitions.
## Fix Focus Areas
- crates/coldvox-gui/src/lib/overlayBridge.ts[47-70]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Docs missing STT command behavior 📘 Rule violation ⚙ Maintainability
Description
New Tauri STT pipeline commands are added, but no accompanying documentation updates are included in
this PR. This can leave contributors without guidance on the new command lifecycle and intended
integration points.
Code

crates/coldvox-gui/src-tauri/src/lib.rs[R138-207]

+/// Feed a live partial transcript update from the STT pipeline.
+/// The overlay stays in Listening state and displays the provisional text.
+#[tauri::command]
+fn update_partial_transcript(
+    text: String,
+    runtime: State<'_, OverlayRuntime>,
+    window: WebviewWindow,
+    app: AppHandle,
+) -> CommandResult {
+    let snapshot =
+        runtime.with_model(|model| model.apply_partial_transcript(&text, None));
+    emit_and_resize(
+        &app,
+        &window,
+        &snapshot,
+        "stt-partial",
+    )
+}
+
+/// Feed a final transcript from the STT pipeline.
+/// Moves partial to final and transitions to Ready.
+#[tauri::command]
+fn update_final_transcript(
+    text: String,
+    runtime: State<'_, OverlayRuntime>,
+    window: WebviewWindow,
+    app: AppHandle,
+) -> CommandResult {
+    let snapshot =
+        runtime.with_model(|model| model.apply_final_transcript(&text, None));
+    emit_and_resize(
+        &app,
+        &window,
+        &snapshot,
+        "stt-final",
+    )
+}
+
+/// Transition the overlay to Processing state (STT is finalizing the utterance).
+#[tauri::command]
+fn set_overlay_processing(
+    runtime: State<'_, OverlayRuntime>,
+    window: WebviewWindow,
+    app: AppHandle,
+) -> CommandResult {
+    let snapshot = runtime.with_model(|model| model.apply_processing_state(None));
+    emit_and_resize(&app, &window, &snapshot, "stt-processing")
+}
+
+/// Transition the overlay to Listening state (new speech segment started).
+#[tauri::command]
+fn set_overlay_listening(
+    runtime: State<'_, OverlayRuntime>,
+    window: WebviewWindow,
+    app: AppHandle,
+) -> CommandResult {
+    let snapshot = runtime.with_model(|model| model.apply_listening_state(None));
+    emit_and_resize(&app, &window, &snapshot, "stt-listening")
+}
+
+/// Stop real capture and return to Idle, clearing transcript state.
+#[tauri::command]
+fn stop_overlay_capture(
+    runtime: State<'_, OverlayRuntime>,
+    window: WebviewWindow,
+    app: AppHandle,
+) -> CommandResult {
+    let snapshot = runtime.with_model(|model| model.stop_capture());
+    emit_and_resize(&app, &window, &snapshot, "capture-stopped")
+}
Evidence
PR Compliance ID 7 requires documentation updates when behavior/direction changes; this PR adds
multiple new runtime commands (update_partial_transcript, update_final_transcript,
set_overlay_processing, set_overlay_listening, stop_overlay_capture) that define the STT
overlay lifecycle.

AGENTS.md
crates/coldvox-gui/src-tauri/src/lib.rs[138-207]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The PR adds new Tauri commands and overlay lifecycle behavior for STT partial/final transcripts, but there is no corresponding documentation update explaining how/when to use these commands.
## Issue Context
The new commands establish the intended runtime seam for pipeline integration and will be used by future host-side wiring.
## Fix Focus Areas
- crates/coldvox-gui/src-tauri/src/lib.rs[138-207]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


4. Stale partial flush after final🐞 Bug ☼ Reliability
Description
queuePartialTranscript schedules a delayed updatePartialTranscript, but calling
updateFinalTranscript/setOverlayProcessing/setOverlayListening does not cancel that timer, so
a late flush can apply a partial after a final/processing transition. This can revert the overlay
status back to Listening and reintroduce partial text out of order.
Code

crates/coldvox-gui/src/hooks/useOverlayShell.ts[R105-115]

+  const queuePartialTranscript = useCallback(
+    (text: string) => {
+      pendingPartialRef.current = text;
+      if (flushTimerRef.current !== null) {
+        clearTimeout(flushTimerRef.current);
+      }
+      // Flush after 80 ms of no new partials — balances latency vs. reduce repaints.
+      flushTimerRef.current = setTimeout(flushPartial, 80);
+    },
+    [flushPartial],
+  );
Evidence
The hook intentionally delays partial updates by 80ms and then calls updatePartialTranscript.
There is a cancelPendingPartial() helper, but it is only used on unmount; none of the other
exported pipeline transition helpers cancel pending partials. On the backend,
apply_partial_transcript always sets status = Listening while apply_final_transcript sets
status = Ready, so a late partial can overwrite the status transition.

crates/coldvox-gui/src/hooks/useOverlayShell.ts[90-115]
crates/coldvox-gui/src/hooks/useOverlayShell.ts[117-131]
crates/coldvox-gui/src/hooks/useOverlayShell.ts[132-147]
crates/coldvox-gui/src-tauri/src/state.rs[116-134]
crates/coldvox-gui/src-tauri/src/state.rs[136-156]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A pending debounced partial flush can fire after a subsequent pipeline transition (final/processing/listening/stop), applying an out-of-order partial update.
### Issue Context
- `queuePartialTranscript()` schedules `flushPartial` after 80ms.
- `cancelPendingPartial()` exists but is only used during unmount cleanup.
- The public methods `updateFinalTranscript`, `setOverlayProcessing`, `setOverlayListening`, and `stopOverlayCapture` do not cancel pending partials.
### Fix Focus Areas
- crates/coldvox-gui/src/hooks/useOverlayShell.ts[90-147]
### Suggested fix
Before invoking any non-partial pipeline transition, cancel or flush pending partial work. For example:
- Call `cancelPendingPartial()` at the start of `updateFinalTranscript`, `setOverlayProcessing`, `setOverlayListening`, and `stopOverlayCapture` wrappers.
- Optionally call `flushPartial()` immediately before `updateFinalTranscript()` so the last partial is not dropped.
This ensures state transitions can’t be undone by a delayed partial timer.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Advisory comments

5. Paused not reset consistently🐞 Bug ≡ Correctness
Description
The new pipeline state transition methods do not reset snapshot.paused, so a previously-paused
state can persist into Listening/Processing/Ready snapshots produced by pipeline commands. This can
mislabel the Pause/Resume UI and leaves snapshot state inconsistent across transitions.
Code

crates/coldvox-gui/src-tauri/src/state.rs[R171-183]

+    /// Transition to Listening state (new utterance started).
+    pub fn apply_listening_state(&mut self, status_detail: Option<&str>) -> OverlaySnapshot {
+        self.snapshot.status = OverlayStatus::Listening;
+        self.snapshot.partial_transcript.clear();
+        self.snapshot.final_transcript.clear();
+        if let Some(detail) = status_detail {
+            self.snapshot.status_detail = detail.to_string();
+        } else {
+            self.snapshot.status_detail = "Listening for speech.".to_string();
+        }
+        self.snapshot.expanded = true;
+        self.snapshot()
+    }
Evidence
stop_capture() explicitly sets paused = false, but apply_listening_state (and the other
newly-added pipeline transitions) do not touch paused. The UI derives the Pause/Resume label from
snapshot.paused, and the button is always rendered, so stale paused state will be visible to
users.

crates/coldvox-gui/src-tauri/src/state.rs[171-183]
crates/coldvox-gui/src-tauri/src/state.rs[185-196]
crates/coldvox-gui/src/components/OverlayShell.tsx[52-53]
crates/coldvox-gui/src/components/OverlayShell.tsx[190-199]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Pipeline-oriented state transitions leave `snapshot.paused` unchanged, which can carry stale demo pause state into real pipeline snapshots.
### Issue Context
`stop_capture()` already clears `paused`, implying pipeline transitions should generally not preserve the paused flag.
### Fix Focus Areas
- crates/coldvox-gui/src-tauri/src/state.rs[116-183]
### Suggested fix
Set `self.snapshot.paused = false;` in pipeline transitions such as `apply_listening_state` (and likely `apply_processing_state` / `apply_final_transcript` / `apply_partial_transcript`) to keep snapshot semantics consistent and avoid stale UI pause state.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

Copy link
Copy Markdown

@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: 61107a982e

ℹ️ 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".

// Pipeline wiring — for real STT integration.
// queuePartialTranscript debounces rapid partials; flushPartial sends immediately.
queuePartialTranscript,
updateFinalTranscript: (text: string) => runCommand(() => updateFinalTranscript(text)),
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Cancel pending partial flush before finalizing transcript

queuePartialTranscript schedules a delayed updatePartialTranscript call, but updateFinalTranscript is invoked directly without clearing that timer. If STT emits a final result within 80ms of the last partial (a common path), the queued partial is sent after the final and can overwrite the snapshot back to listening with stale partial text. This creates out-of-order state transitions and can hide the committed final transcript.

Useful? React with 👍 / 👎.

Comment on lines +173 to +175
self.snapshot.status = OverlayStatus::Listening;
self.snapshot.partial_transcript.clear();
self.snapshot.final_transcript.clear();
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Clear error message when transitioning to listening

apply_listening_state updates status/text but leaves error_message untouched, so a previous command failure can persist as an error badge even after the overlay has successfully re-entered listening. Because the UI renders errorMessage whenever it is non-null, this produces a misleading mixed state (listening + old error) until another method explicitly clears it.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Wires the GUI overlay shell to a new “real STT pipeline” command seam by adding Tauri commands + frontend bridge helpers, and introduces debounced partial transcript updates to reduce UI churn during rapid STT output.

Changes:

  • Added 5 new Tauri commands and corresponding OverlayModel state transitions for partial/final transcript + lifecycle state changes.
  • Added frontend bridge helpers to invoke the new commands from React.
  • Updated useOverlayShell with an 80ms debounce/flush path for partial transcripts and unmount cleanup.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
crates/coldvox-gui/src/lib/overlayBridge.ts Adds new invoke helpers for partial/final transcript and overlay state transitions.
crates/coldvox-gui/src/hooks/useOverlayShell.ts Adds debounced partial transcript queuing and exposes new pipeline wiring helpers.
crates/coldvox-gui/src/hooks/useOverlayShell.test.tsx Extends bridge mocks for new pipeline wiring functions.
crates/coldvox-gui/src-tauri/src/state.rs Adds OverlayModel methods for partial/final transcript updates and state transitions + some unit tests.
crates/coldvox-gui/src-tauri/src/lib.rs Registers new Tauri commands and maps them to the new OverlayModel methods.

Comment on lines +47 to +67
/// Feed a live partial transcript update from the STT pipeline.
export function updatePartialTranscript(text: string): Promise<OverlaySnapshot> {
return invoke<OverlaySnapshot>("update_partial_transcript", { text });
}

/// Feed a final transcript from the STT pipeline.
export function updateFinalTranscript(text: string): Promise<OverlaySnapshot> {
return invoke<OverlaySnapshot>("update_final_transcript", { text });
}

/// Transition the overlay to Processing state.
export function setOverlayProcessing(): Promise<OverlaySnapshot> {
return invoke<OverlaySnapshot>("set_overlay_processing");
}

/// Transition the overlay to Listening state.
export function setOverlayListening(): Promise<OverlaySnapshot> {
return invoke<OverlaySnapshot>("set_overlay_listening");
}

/// Stop real capture and return to Idle.
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

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

TypeScript /// lines here are triple-slash-style comments, which are typically reserved for TS reference directives and are not used elsewhere in this codebase. Please switch these to normal // comments (or remove them) to avoid tooling/lint confusion and keep style consistent.

Suggested change
/// Feed a live partial transcript update from the STT pipeline.
export function updatePartialTranscript(text: string): Promise<OverlaySnapshot> {
return invoke<OverlaySnapshot>("update_partial_transcript", { text });
}
/// Feed a final transcript from the STT pipeline.
export function updateFinalTranscript(text: string): Promise<OverlaySnapshot> {
return invoke<OverlaySnapshot>("update_final_transcript", { text });
}
/// Transition the overlay to Processing state.
export function setOverlayProcessing(): Promise<OverlaySnapshot> {
return invoke<OverlaySnapshot>("set_overlay_processing");
}
/// Transition the overlay to Listening state.
export function setOverlayListening(): Promise<OverlaySnapshot> {
return invoke<OverlaySnapshot>("set_overlay_listening");
}
/// Stop real capture and return to Idle.
// Feed a live partial transcript update from the STT pipeline.
export function updatePartialTranscript(text: string): Promise<OverlaySnapshot> {
return invoke<OverlaySnapshot>("update_partial_transcript", { text });
}
// Feed a final transcript from the STT pipeline.
export function updateFinalTranscript(text: string): Promise<OverlaySnapshot> {
return invoke<OverlaySnapshot>("update_final_transcript", { text });
}
// Transition the overlay to Processing state.
export function setOverlayProcessing(): Promise<OverlaySnapshot> {
return invoke<OverlaySnapshot>("set_overlay_processing");
}
// Transition the overlay to Listening state.
export function setOverlayListening(): Promise<OverlaySnapshot> {
return invoke<OverlaySnapshot>("set_overlay_listening");
}
// Stop real capture and return to Idle.

Copilot uses AI. Check for mistakes.
Comment on lines +140 to +146
// Pipeline wiring — for real STT integration.
// queuePartialTranscript debounces rapid partials; flushPartial sends immediately.
queuePartialTranscript,
updateFinalTranscript: (text: string) => runCommand(() => updateFinalTranscript(text)),
setOverlayProcessing: () => runCommand(setOverlayProcessing),
setOverlayListening: () => runCommand(setOverlayListening),
stopOverlayCapture: () => runCommand(stopOverlayCapture),
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

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

The return block comment says “flushPartial sends immediately”, but flushPartial isn’t returned from the hook, so callers can’t actually flush immediately. Either export flushPartial (and/or a cancel function) alongside queuePartialTranscript, or adjust the comment/API so they match.

Copilot uses AI. Check for mistakes.
Comment on lines +90 to +115
// Debounce-flush partial transcript updates to avoid flooding the shell on rapid STT output.
const pendingPartialRef = useRef<string | null>(null);
const flushTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);

const flushPartial = useCallback(() => {
const text = pendingPartialRef.current;
if (text === null) return;
pendingPartialRef.current = null;
if (flushTimerRef.current !== null) {
clearTimeout(flushTimerRef.current);
flushTimerRef.current = null;
}
void runCommand(() => updatePartialTranscript(text));
}, [runCommand]);

const queuePartialTranscript = useCallback(
(text: string) => {
pendingPartialRef.current = text;
if (flushTimerRef.current !== null) {
clearTimeout(flushTimerRef.current);
}
// Flush after 80 ms of no new partials — balances latency vs. reduce repaints.
flushTimerRef.current = setTimeout(flushPartial, 80);
},
[flushPartial],
);
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

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

The new debounce/flush behavior for partial transcripts isn’t covered by tests in this file. Please add a test that verifies rapid queuePartialTranscript calls only invoke updatePartialTranscript once after ~80ms (with the latest text), and that unmount cleanup cancels a pending flush.

Copilot uses AI. Check for mistakes.
if (flushTimerRef.current !== null) {
clearTimeout(flushTimerRef.current);
}
// Flush after 80 ms of no new partials — balances latency vs. reduce repaints.
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

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

Minor grammar nit: “balances latency vs. reduce repaints” reads ungrammatical. Consider rewording (e.g., “balancing latency vs. reducing repaints”) to keep inline docs clear.

Suggested change
// Flush after 80 ms of no new partials — balances latency vs. reduce repaints.
// Flush after 80 ms of no new partials — balancing latency vs. reducing repaints.

Copilot uses AI. Check for mistakes.
Comment on lines +160 to +166
self.snapshot.status = OverlayStatus::Processing;
if let Some(detail) = status_detail {
self.snapshot.status_detail = detail.to_string();
} else {
self.snapshot.status_detail =
"Processing the utterance into a committed transcript.".to_string();
}
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

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

apply_processing_state sets status/status_detail but doesn’t clear error_message or reset paused. If the model was previously in Error or paused demo state, the UI can incorrectly keep showing a stale error/paused flag after transitioning to Processing. Recommend clearing error_message and forcing paused = false in this transition (similar to other state updates).

Suggested change
self.snapshot.status = OverlayStatus::Processing;
if let Some(detail) = status_detail {
self.snapshot.status_detail = detail.to_string();
} else {
self.snapshot.status_detail =
"Processing the utterance into a committed transcript.".to_string();
}
self.snapshot.status = OverlayStatus::Processing;
self.snapshot.paused = false;
if let Some(detail) = status_detail {
self.snapshot.status_detail = detail.to_string();
} else {
self.snapshot.status_detail =
"Processing the utterance into a committed transcript.".to_string();
}
self.snapshot.error_message = None;

Copilot uses AI. Check for mistakes.
Comment on lines +171 to +182
/// Transition to Listening state (new utterance started).
pub fn apply_listening_state(&mut self, status_detail: Option<&str>) -> OverlaySnapshot {
self.snapshot.status = OverlayStatus::Listening;
self.snapshot.partial_transcript.clear();
self.snapshot.final_transcript.clear();
if let Some(detail) = status_detail {
self.snapshot.status_detail = detail.to_string();
} else {
self.snapshot.status_detail = "Listening for speech.".to_string();
}
self.snapshot.expanded = true;
self.snapshot()
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

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

apply_listening_state clears transcripts but doesn’t clear error_message or reset paused. That can leave the overlay showing an old error/paused state even though it’s listening again. Consider setting error_message = None and paused = false when entering Listening via the real pipeline.

Copilot uses AI. Check for mistakes.
Comment on lines +269 to +307
#[test]
fn apply_partial_transcript_updates_text_and_keeps_listening() {
let mut model = OverlayModel::default();
model.apply_listening_state(None);

let snap1 = model.apply_partial_transcript("hello", None);
assert_eq!(snap1.partial_transcript, "hello");
assert_eq!(snap1.status, OverlayStatus::Listening);
assert!(snap1.final_transcript.is_empty());

let snap2 = model.apply_partial_transcript("hello world", None);
assert_eq!(snap2.partial_transcript, "hello world");
assert_eq!(snap2.status, OverlayStatus::Listening);
}

#[test]
fn apply_final_transcript_moves_partial_to_final_and_transitions_to_ready() {
let mut model = OverlayModel::default();
model.apply_listening_state(None);
model.apply_partial_transcript("hello world", None);

let snap = model.apply_final_transcript("hello world", None);
assert!(snap.partial_transcript.is_empty());
assert_eq!(snap.final_transcript, "hello world");
assert_eq!(snap.status, OverlayStatus::Ready);
}

#[test]
fn stop_capture_clears_all_transcript_state() {
let mut model = OverlayModel::default();
model.apply_listening_state(None);
model.apply_partial_transcript("partial text", None);

let snap = model.stop_capture();
assert_eq!(snap.status, OverlayStatus::Idle);
assert!(snap.partial_transcript.is_empty());
assert!(snap.final_transcript.is_empty());
assert!(snap.error_message.is_none());
}
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

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

PR description says the five new OverlayModel pipeline methods “all have unit tests”, but the added tests only cover partial/final/stop_capture; there’s no unit test for apply_processing_state or apply_listening_state. Either add coverage for those transitions or update the PR description to match reality.

Copilot uses AI. Check for mistakes.
…ions)

Bugs fixed:
- stop_capture now increments demo_token so an in-flight demo driver
  thread exits its loop instead of overwriting the Idle snapshot
- Pipeline state transitions (apply_partial/final/processing/listening)
  reset the paused flag so demo pause state cannot leak into real
  capture sessions
- queuePartialTranscript timer is cancelled before any non-partial
  transition (updateFinalTranscript, setOverlayProcessing,
  setOverlayListening, stopOverlayCapture) to prevent a delayed
  80ms flush from overwriting a status change

Rule violations addressed:
- Added CHANGELOG.md entry for the Phase 3C STT pipeline command wiring
- Added two unit tests: stop_capture_bumps_demo_token_and_stops_demo_driver
  and pipeline_transitions_reset_paused_flag
@qodo-code-review
Copy link
Copy Markdown

qodo-code-review bot commented Apr 2, 2026

CI Feedback 🧐

(Feedback updated until commit 4ce8476)

A test triggered by this PR failed. Here is an AI-generated analysis of the failure:

Action: Validate documentation changes

Failed stage: Regenerate docs index and enforce commit [❌]

Failed test name: ""

Failure summary:

The action failed in the uv run scripts/build_docs_index.py step because the generated docs/index.md
did not match the committed version (the file was stale).
- The workflow regenerates docs/index.md
and then checks git diff --quiet -- docs/index.md; the diff was non-empty, so it emitted
::error::docs/index.md is stale... and exited with code 1 (see log lines 416-420, 433-434, 482).
-
The regenerated index also reports [WARN] 13 docs have invalid frontmatter and lists multiple docs
with missing frontmatter and/or missing required info keys (e.g., freshness/preservation), which
changes the generated index content and contributes to the diff (lines 432, 467-481).

Relevant error logs:
1:  ##[group]Runner Image Provisioner
2:  Hosted Compute Agent
...

136:  * [new tag]         anchor/backup-anchor-oct-06-2025-20251007-234638-2025-10-11 -> anchor/backup-anchor-oct-06-2025-20251007-234638-2025-10-11
137:  * [new tag]         anchor/backup-wip-anchor-oct-06-2025-20251007-234347-2025-10-11 -> anchor/backup-wip-anchor-oct-06-2025-20251007-234347-2025-10-11
138:  * [new tag]         anchor/ci-format-advisory-2025-10-11     -> anchor/ci-format-advisory-2025-10-11
139:  * [new tag]         anchor/ci-vosk-checksum-fix-2025-10-11   -> anchor/ci-vosk-checksum-fix-2025-10-11
140:  * [new tag]         anchor/cleanup-2025-10-11                -> anchor/cleanup-2025-10-11
141:  * [new tag]         anchor/codex-build-coldvox-coverage-dashboard-2025-10-11 -> anchor/codex-build-coldvox-coverage-dashboard-2025-10-11
142:  * [new tag]         anchor/copilot-cleanup-repo-structure-2025-10-11 -> anchor/copilot-cleanup-repo-structure-2025-10-11
143:  * [new tag]         anchor/copilot-fix-cooldowns-per-app-logic-2025-10-11 -> anchor/copilot-fix-cooldowns-per-app-logic-2025-10-11
144:  * [new tag]         anchor/copilot-review-dependencies-and-docs-2025-10-11 -> anchor/copilot-review-dependencies-and-docs-2025-10-11
145:  * [new tag]         anchor/doc-create-master-plan-2025-10-11 -> anchor/doc-create-master-plan-2025-10-11
146:  * [new tag]         anchor/feat-breakdown-plan-into-issues-2025-10-11 -> anchor/feat-breakdown-plan-into-issues-2025-10-11
147:  * [new tag]         anchor/feat-coverage-dashboard-2025-10-11 -> anchor/feat-coverage-dashboard-2025-10-11
148:  * [new tag]         anchor/feature-pr-review-fixes-2025-10-08-2025-10-11 -> anchor/feature-pr-review-fixes-2025-10-08-2025-10-11
149:  * [new tag]         anchor/feature-test-and-doc-fixes-1-2025-10-11 -> anchor/feature-test-and-doc-fixes-1-2025-10-11
150:  * [new tag]         anchor/fix-atspi-focus-detection-2025-10-11 -> anchor/fix-atspi-focus-detection-2025-10-11
151:  * [new tag]         anchor/fix-text-compilation-errors-clean-2025-10-11 -> anchor/fix-text-compilation-errors-clean-2025-10-11
152:  * [new tag]         anchor/fix-vosk-cache-paths-2025-10-11   -> anchor/fix-vosk-cache-paths-2025-10-11
...

402:  Python3_ROOT_DIR: /opt/hostedtoolcache/Python/3.12.13/x64
403:  LD_LIBRARY_PATH: /opt/hostedtoolcache/Python/3.12.13/x64/lib
404:  OPENAI_API_KEY: 
405:  OPENAI_BASE_URL: 
406:  OPENAI_MODEL: 
407:  ##[endgroup]
408:  warning: The `tool.uv.dev-dependencies` field (used in `pyproject.toml`) is deprecated and will be removed in a future release; use `dependency-groups.dev` instead
409:  [OK] Wrote packet: .artifacts/docs_semantic_review_packet.json
410:  [OK] Wrote prompt: .artifacts/docs_semantic_review_prompt.md
411:  [INFO] Changed docs detected: 0
412:  [INFO] Docs included in packet: 0
413:  ##[warning]OPENAI_API_KEY not set; skipping provider-backed semantic review.
414:  ##[group]Run uv run scripts/build_docs_index.py
415:  �[36;1muv run scripts/build_docs_index.py�[0m
416:  �[36;1mif ! git diff --quiet -- docs/index.md; then�[0m
417:  �[36;1m  echo "::error::docs/index.md is stale. Run: python scripts/build_docs_index.py and commit the result."�[0m
418:  �[36;1m  git diff -- docs/index.md || true�[0m
419:  �[36;1m  exit 1�[0m
420:  �[36;1mfi�[0m
421:  shell: /usr/bin/bash -e {0}
422:  env:
423:  pythonLocation: /opt/hostedtoolcache/Python/3.12.13/x64
424:  PKG_CONFIG_PATH: /opt/hostedtoolcache/Python/3.12.13/x64/lib/pkgconfig
425:  Python_ROOT_DIR: /opt/hostedtoolcache/Python/3.12.13/x64
426:  Python2_ROOT_DIR: /opt/hostedtoolcache/Python/3.12.13/x64
427:  Python3_ROOT_DIR: /opt/hostedtoolcache/Python/3.12.13/x64
428:  LD_LIBRARY_PATH: /opt/hostedtoolcache/Python/3.12.13/x64/lib
429:  ##[endgroup]
430:  warning: The `tool.uv.dev-dependencies` field (used in `pyproject.toml`) is deprecated and will be removed in a future release; use `dependency-groups.dev` instead
431:  wrote docs/index.md
432:  [WARN] 13 docs have invalid frontmatter
433:  ##[error]docs/index.md is stale. Run: python scripts/build_docs_index.py and commit the result.
434:  diff --git a/docs/index.md b/docs/index.md
...

467:  +| `docs/dev/commands.md` | missing frontmatter | Add frontmatter |
468:  | `docs/implementation-plans/phase1-audio-quality-monitoring.md` | missing info keys (grace period ended) | Add freshness and preservation |
469:  | `docs/issues/audio-quality-monitoring.md` | missing info keys (grace period ended) | Add freshness and preservation |
470:  -| `docs/plans/parakeet-http-remote-integration-spec.md` | missing info keys (grace period ended) | Add freshness and preservation |
471:  +| `docs/plans/current-status.md` | missing info keys (grace period ended) | Add freshness and preservation |
472:  +| `docs/plans/parakeet-http-remote-integration-spec.md` | missing frontmatter | Add frontmatter |
473:  | `docs/plans/windows-multi-agent-recovery.md` | missing info keys (grace period ended) | Add freshness and preservation |
474:  | `docs/research/AlternateGUIToolingresearch.md` | missing info keys (grace period ended) | Add freshness and preservation |
475:  | `docs/research/dependency-audit-report-2025-02-09.md` | missing frontmatter | Add frontmatter |
476:  | `docs/research/dependency-audit-report-2026-03-24.md` | missing frontmatter | Add frontmatter |
477:  | `docs/research/exhaustive-dependency-audit-2026-03-24.md` | missing frontmatter | Add frontmatter |
478:  | `docs/sessions/2026-03-25-windows-stability-recovery.md` | missing frontmatter | Add frontmatter |
479:  +| `docs/standards/agent-rules.md` | missing frontmatter | Add frontmatter |
480:  | `docs/standards.md` | missing info keys (grace period ended) | Add freshness and preservation |
481:  | `docs/tasks/dependency-audit-plan.md` | missing frontmatter | Add frontmatter |
482:  ##[error]Process completed with exit code 1.
483:  Post job cleanup.

@Coldaine Coldaine merged commit 5073d39 into main Apr 3, 2026
2 of 8 checks passed
@Coldaine Coldaine deleted the worker/coldvox/w-a8df-3c-partial-transcript branch April 3, 2026 12:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants