Skip to content

fix(widget): hide on app load, bottom-center position, exclude from window-state restore - #104

Merged
debpalash merged 1 commit into
mainfrom
fix/dictation-pill-hide-and-position
May 20, 2026
Merged

fix(widget): hide on app load, bottom-center position, exclude from window-state restore#104
debpalash merged 1 commit into
mainfrom
fix/dictation-pill-hide-and-position

Conversation

@debpalash

@debpalash debpalash commented May 20, 2026

Copy link
Copy Markdown
Owner

Three issues with the dictation pill (Whisper-Flow / Ghost-Pepper style):

  1. Pill appeared on app load even though `.visible(false)` and no shortcut press.
  2. When shown, it positioned at top-center instead of bottom-center.
  3. The "Ready — hold shortcut to speak" idle label rendered inside the pill even when no recording was active.

Root causes & fixes

1. tauri-plugin-window-state was restoring widget visibility

If the user had the widget visible when they quit (mid-dictation, or via the tray "Start Dictation" with main window also up), the plugin saved `visible: true` and restored it on next launch — overriding `WebviewWindowBuilder.visible(false)`.

Fix: add the widget label to the plugin's denylist; its state is never persisted. Belt-and-braces: explicit `win.hide()` on the widget during studio-mode and pill-mode startup, so any other plugin or stale state can't sneak the window in.

2. Position was hard-coded to top-center

Changed `LogicalPosition::new(x, 60.0)` (top) to a computed bottom-center position: `y = logical_screen_height - 64 - 80` (80 px margin clears macOS dock + Windows taskbar + most Linux panels). Same math in all three places it's set — identical behavior on every platform per the new CLAUDE.md "default features work on every platform" rule.

3. Idle label rendered visually

`CaptureWidget.jsx` now returns `null` when `state === 'idle'`. Listeners stay mounted (hold-to-talk wiring is preserved), only the visual pill DOM disappears.

Plus: two durable rules locked into CLAUDE.md

These surfaced in conversation but are durable across all future work:

  • Default features must work on every platform. Platform-divergent defaults are a P0 bug; platform-only features must go behind explicit user opt-in (Settings toggle, env var, or CLI flag). No "macOS only" or "Windows only" defaults.
  • No RC, no ceremony. v0.3.0 ships continuous-to-main; tag when actually useful; no v0.4 deferrals while v0.3.0 is open.

Test plan

🤖 Generated with Claude Code

Summary by CodeRabbit

Bug Fixes

  • Fixed dictation widget from displaying unexpectedly on application startup
  • Updated dictation widget positioning to appear at bottom-center of screen
  • Refined capture pill slide-in animation behavior

Review Change Stack

…indow-state restore

Three issues with the dictation pill (Whisper-Flow / Ghost-Pepper style):
1. Pill appeared on app load even though `.visible(false)` and the global
   shortcut hadn't been pressed.
2. When shown, it positioned at top-center instead of bottom-center.
3. The "Ready — hold shortcut to speak" idle label rendered inside the pill
   even when no recording was active.

Root causes & fixes:

**(1) `tauri-plugin-window-state` was restoring widget visibility.**
If the user had the widget visible when they quit the app (mid-dictation,
or by clicking the tray's "Start Dictation" while a window was up), the
plugin saved `visible: true` and restored it on next launch — overriding
the `WebviewWindowBuilder.visible(false)`. Fix: add the widget label to
the plugin's denylist, so its state is never persisted. Belt-and-braces:
explicit `win.hide()` on the widget during studio-mode and pill-mode
startup, so any other plugin or stale state can't sneak the window in.

**(2) Position was hard-coded to top-center.**
Changed `LogicalPosition::new(x, 60.0)` (top) to a computed bottom-center
position: `y = logical_screen_height - 64 - 80` (80 px margin clears
macOS dock + Windows taskbar + most Linux panels). Same math in all
three places it's set (global-shortcut handler, tray dictate, pill-mode
pre-position) — identical behavior on macOS/Windows/Linux per the new
CLAUDE.md "default features work on every platform" rule.

**(3) Idle label rendered visually.**
`CaptureWidget.jsx` now returns `null` when `state === 'idle'`. Listeners
stay mounted (hold-to-talk wiring is preserved), only the visual pill DOM
disappears. The slide-in animation triggers on the natural unmount→mount
when state flips out of idle.

Also: lock in two durable rules surfaced in this session:
- CLAUDE.md: "Default features must work on every platform" — platform-
  divergent defaults are a P0 bug; platform-only features must go behind
  explicit opt-in.
- CLAUDE.md: "No RC, no ceremony" — v0.3.0 ships continuous-to-main; tag
  when actually useful; no v0.4 deferrals while v0.3.0 is open.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented May 20, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

Project constraints add cross-platform default behavior rules and update release policy. Tauri backend now excludes the widget window from state persistence, positions it at computed bottom-center coordinates when activated via global shortcut or tray menu, and defensively hides it on startup. Frontend component returns null when idle, removing the "ready" pill UI until state transitions, with animation keyframe adjusted to match new positioning.

Changes

Widget Visibility, Positioning, and Idle Handling

Layer / File(s) Summary
Project constraints and release policy
CLAUDE.md
Cross-platform default behavior constraint added (P0-bug treatment for platform-specific defaults); release policy updated to continuous-to-main, single v0.3.0 tag, no RC/ceremony phases.
Widget window state and positioning (Tauri)
frontend/src-tauri/src/lib.rs
Window-state persistence excludes widget window. Global shortcut and tray menu handlers compute bottom-center monitor-based positioning instead of fixed top offset. Pill-mode and studio-mode startup both defensively hide widget and pre-position to bottom-center.
Capture widget idle state and animation
frontend/src/components/CaptureWidget.jsx, frontend/src/components/CaptureWidget.css
Component returns null when idle, removing pill DOM until recording/transcription states. Fallback "Ready" label removed. Animation pill-slide-in keyframe translateY adjusted from -12px to 8px.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

  • debpalash/OmniVoice-Studio#83: Directly related widget-on-idle behavior fix; both PRs prevent widget visibility during pill-mode startup and instead show it only on activation.
  • debpalash/OmniVoice-Studio#40: Introduces the widget window and CaptureWidget hiding/reset logic; this PR further refines when/how the widget is shown and positioned.

Poem

🐰 A pill that slides from bottom to the top,
No idle chatter—wait till talk won't stop,
Cross-platform steady, one release so true,
Widget behaves the same for me and you!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main changes: hiding the widget on app load, repositioning to bottom-center, and excluding from window-state restoration.
Description check ✅ Passed The description covers all required template sections: Summary, Changes, Type (Bug fix), Testing, and Checklist. It provides detailed root causes and fixes with clear technical context.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/dictation-pill-hide-and-position

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@frontend/src-tauri/src/lib.rs`:
- Around line 201-207: The computed x/y for positioning the pill can be negative
and is duplicated; create a small helper (e.g., fn clamp_pill_position(size:
tauri::PhysicalSize, scale: f64, pill_w: f64, pill_h: f64) -> (f64,f64)) that
computes logical_w/logical_h, derives desired center-based x and bottom-based y,
then clamps x to the range [0.0, logical_w - pill_w] and y to [0.0, logical_h -
pill_h] before returning a LogicalPosition; replace the inline logic around
win.set_position(...) (the block using logical_w/logical_h, x/y and
tauri::LogicalPosition::new) in all three places with a call to this helper so
positioning is centralized and never places the pill off-screen.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 10f2ca96-c761-4fad-828e-b7d057542294

📥 Commits

Reviewing files that changed from the base of the PR and between f4e4082 and f1bf565.

📒 Files selected for processing (4)
  • CLAUDE.md
  • frontend/src-tauri/src/lib.rs
  • frontend/src/components/CaptureWidget.css
  • frontend/src/components/CaptureWidget.jsx

Comment on lines +201 to 207
let logical_w = size.width as f64 / scale;
let logical_h = size.height as f64 / scale;
let x = (logical_w / 2.0 - 150.0) as i32;
let y = (logical_h - 64.0 - 80.0) as i32;
let _ = win.set_position(tauri::Position::Logical(
tauri::LogicalPosition::new(x as f64, 60.0),
tauri::LogicalPosition::new(x as f64, y as f64),
));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Clamp and centralize widget coordinates before set_position.

The computed x/y can go negative on small or highly scaled displays, which may place the pill partially/off-screen. This same logic is repeated in three places, so it can drift.

Proposed fix
+fn widget_bottom_center_position(logical_w: f64, logical_h: f64) -> tauri::LogicalPosition<f64> {
+    let x = (logical_w / 2.0 - 150.0).max(0.0);
+    let y = (logical_h - 64.0 - 80.0).max(0.0);
+    tauri::LogicalPosition::new(x, y)
+}
- let x = (logical_w / 2.0 - 150.0) as i32;
- let y = (logical_h - 64.0 - 80.0) as i32;
- let _ = win.set_position(tauri::Position::Logical(
-     tauri::LogicalPosition::new(x as f64, y as f64),
- ));
+ let pos = widget_bottom_center_position(logical_w, logical_h);
+ let _ = win.set_position(tauri::Position::Logical(pos));

Also applies to: 359-365, 431-437

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src-tauri/src/lib.rs` around lines 201 - 207, The computed x/y for
positioning the pill can be negative and is duplicated; create a small helper
(e.g., fn clamp_pill_position(size: tauri::PhysicalSize, scale: f64, pill_w:
f64, pill_h: f64) -> (f64,f64)) that computes logical_w/logical_h, derives
desired center-based x and bottom-based y, then clamps x to the range [0.0,
logical_w - pill_w] and y to [0.0, logical_h - pill_h] before returning a
LogicalPosition; replace the inline logic around win.set_position(...) (the
block using logical_w/logical_h, x/y and tauri::LogicalPosition::new) in all
three places with a call to this helper so positioning is centralized and never
places the pill off-screen.

@debpalash
debpalash merged commit aece6f1 into main May 20, 2026
14 of 15 checks passed
@debpalash
debpalash deleted the fix/dictation-pill-hide-and-position branch May 20, 2026 04:20
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