Skip to content

Use the shared floating panel for dictation - #7658

Open
ComputelessComputer wants to merge 1 commit into
chore/anlg-382-system-dictationfrom
chore/anlg-382-dictation-floating-panel
Open

ComputelessComputer wants to merge 1 commit into
chore/anlg-382-system-dictationfrom
chore/anlg-382-dictation-floating-panel

Conversation

@ComputelessComputer

@ComputelessComputer ComputelessComputer commented Sep 16, 2026

Copy link
Copy Markdown
Collaborator

Add optional live words, microphone activity, and cancel/finish controls across desktop platforms. Preserve destination focus and keep preview failures from interrupting recordings. ANLG-382.


Summary by cubic

Implements ANLG-382 by moving dictation into the shared floating panel instead of a dedicated dictation window, so the destination field keeps focus on all desktop platforms. Adds an optional live transcript preview, and dictation keeps recording normally if the preview connection can't be set up.

Details

  • The floating panel now shows microphone amplitude and cancel/finish controls during dictation.
  • Live preview words are inserted only when you finish; stale preview updates are ignored after cancel or finish.
  • Recording starts only after the floating panel has presented itself, so the panel is ready before microphone capture begins.
  • Cloud preview uses a refreshed session token; local preview uses the configured connection without an auth request.

Written for commit 0eb151b. Summary will update on new commits.

Review in cubic

Add optional live words, microphone activity, and cancel/finish controls across desktop platforms. Preserve destination focus and keep preview failures from interrupting recordings. ANLG-382.
@ComputelessComputer
ComputelessComputer added this pull request to stack #7659 September 16, 2026 23:47

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

7 issues found across 260 files

Not reviewed (too large): apps/desktop/src/i18n/locales/af/messages.ts (~2 lines), apps/desktop/src/i18n/locales/am/messages.ts (~2 lines), apps/desktop/src/i18n/locales/ar/messages.ts (~2 lines), apps/desktop/src/i18n/locales/as/messages.ts (~2 lines), apps/desktop/src/i18n/locales/az/messages.ts (~2 lines), apps/desktop/src/i18n/locales/ba/messages.ts (~2 lines), apps/desktop/src/i18n/locales/be/messages.ts (~2 lines), apps/desktop/src/i18n/locales/bg/messages.ts (~2 lines), apps/desktop/src/i18n/locales/bn/messages.ts (~2 lines), apps/desktop/src/i18n/locales/bo/messages.ts (~2 lines), and 40 more - if these are generated or fixture files, add them to ignored paths to exclude them from future reviews.

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/desktop/src/meeting-float/overlay/dictation.tsx">

<violation number="1" location="apps/desktop/src/meeting-float/overlay/dictation.tsx:27">
P2: During live preview, `aria-atomic="true"` makes every partial update re-announce the entire accumulated transcript, causing repeated speech and a growing screen-reader queue. Set this live region to non-atomic or isolate the changing partial text in its own live region.</violation>
</file>

<file name="plugins/dictation/src/recorder.rs">

<violation number="1" location="plugins/dictation/src/recorder.rs:204">
P2: When the preview consumer falls behind capture, the 32-item queue fills and `Preview::send` returns `false` even though the connection is still healthy. This branch permanently drops live preview and reports `previewUnavailable`; distinguish queue-full from closed/fatal errors and disable preview only for the latter.</violation>
</file>

<file name="apps/desktop/src/meeting-float/window-panel.ts">

<violation number="1" location="apps/desktop/src/meeting-float/window-panel.ts:218">
P2: When the post-update `floatingBarShow()` call fails for dictation, this branch returns without logging `shown.error`, unlike the earlier show path. Log the native error before returning so panel-open timeouts can be diagnosed.</violation>
</file>

<file name="apps/desktop/src/dictation/lifecycle.tsx">

<violation number="1" location="apps/desktop/src/dictation/lifecycle.tsx:159">
P1: When cloud live preview is enabled, `auth.getSessionForRequest()` runs before `captureTarget()`, so token-refresh latency can capture a newly focused field instead of the shortcut's original destination. Capture the target before awaiting preview-session authentication.</violation>
</file>

<file name="plugins/dictation/src/preview.rs">

<violation number="1" location="plugins/dictation/src/preview.rs:92">
P2: When a configured realtime provider is omitted from this match, live preview always reports unavailable even though batch transcription supports that provider. Dispatch every supported realtime provider or use a shared adapter registry.</violation>
</file>

<file name="plugins/windows/swift-lib/src/FloatingBarManager.swift">

<violation number="1" location="plugins/windows/swift-lib/src/FloatingBarManager.swift:319">
P2: After the initializer errors are fixed, `layout(matching:)` still cannot identify a compact dictation frame because it has no `isDictation: true` candidates. When dictation ends, `resize` falls through to the inverted expanded fallback, which can move the panel to the wrong anchor; enumerate both dictation values.</violation>
</file>

<file name="plugins/windows/swift-lib/src/FloatingBarView.swift">

<violation number="1" location="plugins/windows/swift-lib/src/FloatingBarView.swift:37">
P2: When a compact dictation bar is dragged and then expanded or finished, the panel shifts by 33 points because its preserved anchor omits the cancel button width. Pass `layout.isDictation` to the manager’s compact control-width calculation so the anchor matches the rendered controls.</violation>
</file>

Note: This PR contains a large number of files. cubic selects up to 200 of the highest-priority eligible files for this review, so some files may not have been reviewed.

Re-trigger cubic

);
const previewSession =
livePreview && isCloudModel
? await auth.getSessionForRequest().catch(() => null)

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: When cloud live preview is enabled, auth.getSessionForRequest() runs before captureTarget(), so token-refresh latency can capture a newly focused field instead of the shortcut's original destination. Capture the target before awaiting preview-session authentication.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/desktop/src/dictation/lifecycle.tsx, line 159:

<comment>When cloud live preview is enabled, `auth.getSessionForRequest()` runs before `captureTarget()`, so token-refresh latency can capture a newly focused field instead of the shortcut's original destination. Capture the target before awaiting preview-session authentication.</comment>

<file context>
@@ -119,15 +154,70 @@ function ActiveDictation({
           );
+        const previewSession =
+          livePreview && isCloudModel
+            ? await auth.getSessionForRequest().catch(() => null)
+            : null;
+        const apiKey = isCloudModel
</file context>

className="px-1 text-[15px] leading-6 break-words whitespace-pre-wrap"
style={{ color: colorScheme === "dark" ? "white" : "rgb(31, 28, 26)" }}
aria-live="polite"
aria-atomic="true"

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: During live preview, aria-atomic="true" makes every partial update re-announce the entire accumulated transcript, causing repeated speech and a growing screen-reader queue. Set this live region to non-atomic or isolate the changing partial text in its own live region.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/desktop/src/meeting-float/overlay/dictation.tsx, line 27:

<comment>During live preview, `aria-atomic="true"` makes every partial update re-announce the entire accumulated transcript, causing repeated speech and a growing screen-reader queue. Set this live region to non-atomic or isolate the changing partial text in its own live region.</comment>

<file context>
@@ -0,0 +1,40 @@
+      className="px-1 text-[15px] leading-6 break-words whitespace-pre-wrap"
+      style={{ color: colorScheme === "dark" ? "white" : "rgb(31, 28, 26)" }}
+      aria-live="polite"
+      aria-atomic="true"
+    >
+      {dictation.text || dictation.partial ? (
</file context>

if let Some(updates) = &updates {
let rms = (samples.iter().map(|s| f64::from(*s).powi(2)).sum::<f64>() / samples.len().max(1) as f64).sqrt();
let _ = updates.send(RecordingUpdate::Amplitude { amplitude: (rms * 8.0).clamp(0.0, 1.0) });
if preview.as_ref().is_some_and(|preview| !preview.send(&samples)) {

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: When the preview consumer falls behind capture, the 32-item queue fills and Preview::send returns false even though the connection is still healthy. This branch permanently drops live preview and reports previewUnavailable; distinguish queue-full from closed/fatal errors and disable preview only for the latter.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At plugins/dictation/src/recorder.rs, line 204:

<comment>When the preview consumer falls behind capture, the 32-item queue fills and `Preview::send` returns `false` even though the connection is still healthy. This branch permanently drops live preview and reports `previewUnavailable`; distinguish queue-full from closed/fatal errors and disable preview only for the latter.</comment>

<file context>
@@ -179,6 +198,14 @@ async fn record_to_file(
+                if let Some(updates) = &updates {
+                    let rms = (samples.iter().map(|s| f64::from(*s).powi(2)).sum::<f64>() / samples.len().max(1) as f64).sqrt();
+                    let _ = updates.send(RecordingUpdate::Amplitude { amplitude: (rms * 8.0).clamp(0.0, 1.0) });
+                    if preview.as_ref().is_some_and(|preview| !preview.send(&samples)) {
+                        preview = None;
+                        let _ = updates.send(RecordingUpdate::PreviewUnavailable);
</file context>

await hideFloatingMeetingPanel();
return false;
}
if (shown.status === "error") return false;

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: When the post-update floatingBarShow() call fails for dictation, this branch returns without logging shown.error, unlike the earlier show path. Log the native error before returning so panel-open timeouts can be diagnosed.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/desktop/src/meeting-float/window-panel.ts, line 218:

<comment>When the post-update `floatingBarShow()` call fails for dictation, this branch returns without logging `shown.error`, unlike the earlier show path. Log the native error before returning so panel-open timeouts can be diagnosed.</comment>

<file context>
@@ -205,6 +209,15 @@ export async function showFloatingMeetingWindow(
+      await hideFloatingMeetingPanel();
+      return false;
+    }
+    if (shown.status === "error") return false;
+  }
+
</file context>
Suggested change
if (shown.status === "error") return false;
if (shown.status === "error") {
console.error("Failed to show floating meeting panel:", shown.error);
return false;
}

"elevenlabs" => listen::<ElevenLabsAdapter>(config, receiver, updates).await,
"gladia" => listen::<GladiaAdapter>(config, receiver, updates).await,
"meta" => listen::<MetaAdapter>(config, receiver, updates).await,
_ => Err(()),

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: When a configured realtime provider is omitted from this match, live preview always reports unavailable even though batch transcription supports that provider. Dispatch every supported realtime provider or use a shared adapter registry.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At plugins/dictation/src/preview.rs, line 92:

<comment>When a configured realtime provider is omitted from this match, live preview always reports unavailable even though batch transcription supports that provider. Dispatch every supported realtime provider or use a shared adapter registry.</comment>

<file context>
@@ -0,0 +1,318 @@
+        "elevenlabs" => listen::<ElevenLabsAdapter>(config, receiver, updates).await,
+        "gladia" => listen::<GladiaAdapter>(config, receiver, updates).await,
+        "meta" => listen::<MetaAdapter>(config, receiver, updates).await,
+        _ => Err(()),
+    }
+}
</file context>

private struct FloatingBarWindowLayout {
let isExpanded: Bool
let showsExpand: Bool
let isDictation: Bool

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: After the initializer errors are fixed, layout(matching:) still cannot identify a compact dictation frame because it has no isDictation: true candidates. When dictation ends, resize falls through to the inverted expanded fallback, which can move the panel to the wrong anchor; enumerate both dictation values.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At plugins/windows/swift-lib/src/FloatingBarManager.swift, line 319:

<comment>After the initializer errors are fixed, `layout(matching:)` still cannot identify a compact dictation frame because it has no `isDictation: true` candidates. When dictation ends, `resize` falls through to the inverted expanded fallback, which can move the panel to the wrong anchor; enumerate both dictation values.</comment>

<file context>
@@ -308,4 +316,11 @@ final class FloatingBarManager {
 private struct FloatingBarWindowLayout {
   let isExpanded: Bool
   let showsExpand: Bool
+  let isDictation: Bool
+}
+
</file context>

static func compactControlsWidth(showsExpand: Bool, isDictation: Bool = false) -> CGFloat {
let recordingControls =
showsExpand ? compactStopWidth + compactGap + compactIconSize : compactSoloStopWidth
return recordingControls + (isDictation ? compactIconSize + compactGap : 0)

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: When a compact dictation bar is dragged and then expanded or finished, the panel shifts by 33 points because its preserved anchor omits the cancel button width. Pass layout.isDictation to the manager’s compact control-width calculation so the anchor matches the rendered controls.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At plugins/windows/swift-lib/src/FloatingBarView.swift, line 37:

<comment>When a compact dictation bar is dragged and then expanded or finished, the panel shifts by 33 points because its preserved anchor omits the cancel button width. Pass `layout.isDictation` to the manager’s compact control-width calculation so the anchor matches the rendered controls.</comment>

<file context>
@@ -31,29 +31,27 @@ enum FloatingBarLayout {
+  static func compactControlsWidth(showsExpand: Bool, isDictation: Bool = false) -> CGFloat {
+    let recordingControls =
+      showsExpand ? compactStopWidth + compactGap + compactIconSize : compactSoloStopWidth
+    return recordingControls + (isDictation ? compactIconSize + compactGap : 0)
   }
 
</file context>

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.

1 participant