Skip to content

feat(overlay): activate main window on orb click (#605)#611

Merged
senamakel merged 5 commits into
tinyhumansai:mainfrom
oxoxDev:feat/605-orb-click-show-window
Apr 17, 2026
Merged

feat(overlay): activate main window on orb click (#605)#611
senamakel merged 5 commits into
tinyhumansai:mainfrom
oxoxDev:feat/605-orb-click-show-window

Conversation

@oxoxDev
Copy link
Copy Markdown
Contributor

@oxoxDev oxoxDev commented Apr 16, 2026

Summary

  • Clicking the overlay orb in idle mode now brings the main app window to the foreground (show + unminimize + focus).
  • Exposes activate_main_window as a Tauri command so the overlay webview can reuse the existing show_main_window helper (the same one the tray icon uses).
  • Works around NSPanel (non-activating) on macOS swallowing synthesized click events by emulating click on mouseup when the pointer stays within a 4px slop.

Problem

The overlay orb is always visible but clicking it had no effect — the main window stayed wherever it was (hidden, minimized, or behind other apps). Users had no way to surface the main window from the orb short of using the Dock / tray icon. See #605.

Two layered root causes had to be fixed:

  1. No command wired up — the overlay couldn't invoke any backend function to show the main window, and the existing show_main_window helper in lib.rs was only reachable from tray / Reopen handlers.
  2. macOS NSPanel quirk — the overlay window is reclassed to NSPanel with NonactivatingPanel style (from [Bug] Voice overlay orb disappears behind fullscreen applications #528, required for fullscreen visibility). That panel doesn't become key on click, so React's synthesized onClick never fires on the orb. Naively adding an onMouseUp handler also didn't work because the existing onMouseDown immediately calls startDragging(), which takes over the native event loop and prevents mouseup from reaching the webview.

Solution

Backend (app/src-tauri/src/lib.rs, app/src-tauri/permissions/allow-core-process.toml)

  • Added a thin #[tauri::command] fn activate_main_window(app: AppHandle) that delegates to the existing show_main_window(&app) helper — same show / unminimize / set_focus flow that the tray icon and macOS Reopen event use.
  • Registered the command in generate_handler![] and added it to the allow-core-process capability so the overlay window can invoke it. Without the capability entry, invoke() from the overlay is silently rejected.

Frontend (app/src/overlay/OverlayApp.tsx)

  • handleOrbClick: idle → invoke('activate_main_window'); active → dismiss bubble (old goIdle behavior).
  • Restructured the mouse handlers so drag is deferred until the pointer actually moves:
    • onMouseDown just records the press position — no startDragging() yet.
    • onMouseMove escalates to startDragging() only once the pointer moves past a 4px slop.
    • onMouseUp fires handleOrbClick if no drag was initiated.
  • This preserves the existing drag-to-reposition and double-click-to-reset behaviors while adding click-to-activate.

Submission Checklist

  • Unit tests — N/A: behavior is user-visible interaction (mouse events + native window activation) that is not meaningful to cover with Vitest. Manual verification done.
  • E2E / integration — N/A: the overlay runs in a separate NSPanel webview with Tauri native drag; current WDIO harness doesn't drive the overlay window.
  • N/A — see above.
  • Doc comments/// doc comment on the new Rust command; JSDoc-style comments on the new TS handlers; a block comment at pressRef explains why drag is deferred.
  • Inline comments — kept minimal; one comment near pressRef captures the NSPanel + startDragging() constraint.

Impact

  • Platforms: Desktop only. Tested on macOS (NSPanel path). Windows/Linux overlays aren't reclassed to NSPanel so click would work there regardless; the new handlers also work on those.
  • Performance: Negligible — one ref write per mousedown, one comparison per mousemove while a press is active.
  • Security: The new command is gated by the existing allow-core-process capability; only the main and overlay windows can invoke it.
  • Migration / compatibility: No schema, storage, or protocol changes. No existing event handlers removed (drag, double-click, hover all preserved).

Related

Summary by CodeRabbit

  • New Features

    • Clicking the orb when idle now activates and brings the main window to the foreground.
    • Added a user-invokable action to activate the main window.
  • Improvements

    • Refined orb interaction: deferred drag start, improved press/move/up handling, and reliable click vs. double-click behavior.
  • Permissions

    • App permissions updated to explicitly allow the main-window activation action.

oxoxDev and others added 2 commits April 16, 2026 23:08
…ai#605)

Exposes the existing show_main_window helper as a Tauri command so the
overlay webview can bring the main window to front. The command is
whitelisted in allow-core-process.toml so the overlay window capability
can invoke it.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The overlay orb had no click behavior. Now clicking it in idle mode
invokes activate_main_window, bringing the main app window to the front
(mirrors the tray icon flow).

Since the overlay is an NSPanel NonactivatingPanel on macOS, React's
synthesized onClick does not fire. Instead we record the press position
on mousedown and emulate click on mouseup when the pointer stayed within
a 4px slop. Dragging is deferred to mousemove past the slop so startDragging
doesn't swallow the mouseup event.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 16, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6cdb6ed5-6b6c-4a71-b93a-748be9a0ad4c

📥 Commits

Reviewing files that changed from the base of the PR and between 4be220a and 0650977.

📒 Files selected for processing (2)
  • app/src-tauri/src/lib.rs
  • app/src/overlay/OverlayApp.tsx

📝 Walkthrough

Walkthrough

Adds a new Tauri command activate_main_window, permits it in core-process permissions, changes show_main_window to return Result<(), String> with explicit error handling, updates tray/reopen handlers, and updates overlay orb input to distinguish clicks from drags and invoke activation when appropriate.

Changes

Cohort / File(s) Summary
Tauri permission
app/src-tauri/permissions/allow-core-process.toml
Added "activate_main_window" to [permission.commands].allow for allow-core-process.
Tauri runtime / window activation
app/src-tauri/src/lib.rs
Changed show_main_window(&AppHandle) to return Result<(), String>; added #[tauri::command] fn activate_main_window(app: AppHandle) -> Result<(), String>; updated tray click and macOS Reopen handling to call and log errors from show_main_window; registered activate_main_window with the invoke handler.
Overlay orb input handling
app/src/overlay/OverlayApp.tsx
Replaced direct onClick with handleOrbClick; added press tracking, movement slop (CLICK_SLOP_PX), deferred drag start, single-click scheduling and double-click handling; new wiring uses mousedown/mousemove/mouseup/double-click and invokes activate_main_window when appropriate.

Sequence Diagram

sequenceDiagram
    actor User
    participant Overlay as "OverlayApp.tsx"
    participant Tauri as "Tauri Runtime"
    participant Backend as "lib.rs::activate_main_window"
    participant Window as "Main Window"

    User->>Overlay: Pointer down / move / up on orb
    Overlay->>Overlay: Distinguish click vs drag (pressRef, CLICK_SLOP_PX)
    alt Click (no drag) & mode == idle
        Overlay->>Tauri: invoke("activate_main_window")
        Tauri->>Backend: activate_main_window(app)
        Backend->>Backend: show_main_window(&app) -> Result
        Backend->>Window: show(), unminimize(), set_focus()
        Window-->>User: Window becomes frontmost / focused
    else Drag or non-idle
        Overlay->>Overlay: start drag or perform idle/non-activate action
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐇 I tapped the orb, a gentle beat,
The window woke and took its seat.
From slop to click, the path was true,
A rabbit's hop brought back the view. ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(overlay): activate main window on orb click (#605)' clearly and concisely summarizes the primary change: adding window activation functionality to orb clicks in the overlay component.
Linked Issues check ✅ Passed The pull request addresses all coding requirements from issue #605: implements orb click handler to activate/show main window, preserves existing drag/double-click interactions, and adds proper backend permission whitelisting.
Out of Scope Changes check ✅ Passed All changes are directly aligned with issue #605 objectives: backend command registration, permission whitelisting, and frontend orb click/drag handling. No unrelated modifications detected.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/src-tauri/src/lib.rs`:
- Around line 409-412: activate_main_window currently swallows all errors;
change its signature to return a Result<(), String> and propagate any failures
from locating the "main" window and from calling Window::show(),
Window::unminimize(), and Window::set_focus() (or from the helper
show_main_window(&app)) by converting their errors into an Err(String) (e.g. via
map_err or ? with From) so the Tauri invoke handler receives a rejected promise;
ensure the invoke registration/handler expects Result and forwards the returned
error to JavaScript.

In `@app/src/overlay/OverlayApp.tsx`:
- Around line 473-483: handleMouseUp currently calls handleOrbClick
synchronously which causes a single-click to fire before the
onDoubleClick={resetPosition}, so change handleMouseUp to schedule the click
action with a short timeout (e.g., 150–250ms) instead of invoking handleOrbClick
immediately: if e.button !== 0 or pressRef.current is null or
pressRef.current.dragStarted then do nothing; otherwise start a timeout (store
it in a ref like singleClickTimerRef) that calls handleOrbClick after the delay.
In resetPosition (the onDoubleClick handler) clear that singleClickTimerRef so
the pending click is cancelled on a double-click; also clear the timer on
unmount and when press becomes null to avoid leaks.
- Around line 444-457: Stale press state can persist if the user releases the
mouse off the orb; update handleMouseMove to detect that the button is no longer
down (check e.buttons === 0 or (e.buttons & 1) === 0) and if so clear
pressRef.current = null and return before any slop checks, and also ensure the
existing onMouseUp handler clears pressRef.current (set to null) so press state
is always reset when the button is released; modify handleDragStart,
handleMouseMove, and onMouseUp references to pressRef and any call to
startDragging() accordingly.
🪄 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

Run ID: 0c9355d9-8c6b-4500-af6d-0f58b1d8dcf4

📥 Commits

Reviewing files that changed from the base of the PR and between 7db7140 and a8530cf.

📒 Files selected for processing (3)
  • app/src-tauri/permissions/allow-core-process.toml
  • app/src-tauri/src/lib.rs
  • app/src/overlay/OverlayApp.tsx

Comment thread app/src-tauri/src/lib.rs Outdated
Comment thread app/src/overlay/OverlayApp.tsx
Comment thread app/src/overlay/OverlayApp.tsx
oxoxDev and others added 2 commits April 16, 2026 23:58
…them (tinyhumansai#605)

`show_main_window` silently logged failures and returned `()`, so the
`activate_main_window` Tauri command could report success on a no-op. Thread
`Result<(), String>` through so JS `invoke().catch()` sees real failures, and
preserve the previous log-on-error behavior at the tray/Reopen call sites
where no caller consumes the result.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ai#605)

Two follow-ups to the deferred-drag pattern:

1. Drop stale pressRef when the primary button is no longer held during
   mousemove. Window-drag / focus changes can steal the mouseup, leaving
   the ref populated so the next idle hover would start a spurious drag.

2. Debounce the synthetic click by 250 ms so a follow-up dblclick can
   cancel it — the double-click-to-reset gesture was firing activate +
   reset together. Clear the timer on dblclick and on unmount.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/src/overlay/OverlayApp.tsx`:
- Around line 446-450: handleDragStart currently records pressRef but doesn't
cancel any pending single-click timer, so a click scheduled on the previous
mouseup can still fire mid-drag; update handleDragStart to also clear the
pending click timeout (e.g., call clearTimeout on your single-click timer ref
such as singleClickTimeoutRef or clickTimeoutRef and set it to null) when a new
mousedown begins, so handleOrbClick cannot run during a subsequent drag; keep
the existing e.button check and pressRef update.
- Around line 197-203: The handler handleOrbClick currently calls
invoke('activate_main_window') unconditionally; wrap that Tauri command call
with an isTauri() runtime check before invoking to ensure it only runs in Tauri
environments (keep the existing .catch error handling). Update the conditional
branch where mode === 'idle' inside handleOrbClick to call
invoke('activate_main_window') only if isTauri() returns true, otherwise skip
the invoke (or use the non-Tauri fallback if needed).
🪄 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

Run ID: 1422a2ad-c71e-4976-bbe7-b135ea142c54

📥 Commits

Reviewing files that changed from the base of the PR and between a8530cf and 4be220a.

📒 Files selected for processing (2)
  • app/src-tauri/src/lib.rs
  • app/src/overlay/OverlayApp.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/src-tauri/src/lib.rs

Comment on lines +197 to +203
/** Click handler for the orb: idle → bring main window to front; active → dismiss bubble. */
const handleOrbClick = useCallback(() => {
if (mode === 'idle') {
console.debug('[overlay] orb clicked while idle — activating main window');
invoke('activate_main_window').catch(err => {
console.error('[overlay] failed to activate main window:', err);
});
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

🧩 Analysis chain

🏁 Script executed:

# Check the current state of the file at the relevant lines
cat -n app/src/overlay/OverlayApp.tsx | sed -n '190,210p'

Repository: tinyhumansai/openhuman

Length of output: 910


🏁 Script executed:

# Check if isTauri is imported and used in the file
rg -A2 -B2 'isTauri' app/src/overlay/OverlayApp.tsx | head -30

Repository: tinyhumansai/openhuman

Length of output: 395


🏁 Script executed:

# Check the tauriCommands wrapper pattern to see if activate_main_window should be wrapped
cat -n app/src/utils/tauriCommands.ts

Repository: tinyhumansai/openhuman

Length of output: 128


🏁 Script executed:

# Check for any existing wrapper for activate_main_window
rg 'activate_main_window' app/src/

Repository: tinyhumansai/openhuman

Length of output: 148


🏁 Script executed:

# Check the CLAUDE.md for actual coding guidelines about Tauri guards
cat CLAUDE.md 2>/dev/null | head -100

Repository: tinyhumansai/openhuman

Length of output: 5935


Guard the new Tauri command call with isTauri().

Line 201 invokes activate_main_window unconditionally. The file already imports isTauri and uses it for similar Tauri calls in resolveCoreSocketUrl(). Per coding guidelines, guard all Tauri usage with explicit runtime checks instead of relying on command failure handling.

Suggested fix
   const handleOrbClick = useCallback(() => {
     if (mode === 'idle') {
+      if (!isTauri()) {
+        console.debug('[overlay] orb clicked while idle outside Tauri — ignoring');
+        return;
+      }
       console.debug('[overlay] orb clicked while idle — activating main window');
-      invoke('activate_main_window').catch(err => {
+      void invoke('activate_main_window').catch(err => {
         console.error('[overlay] failed to activate main window:', err);
       });
     } else {
       goIdle();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/overlay/OverlayApp.tsx` around lines 197 - 203, The handler
handleOrbClick currently calls invoke('activate_main_window') unconditionally;
wrap that Tauri command call with an isTauri() runtime check before invoking to
ensure it only runs in Tauri environments (keep the existing .catch error
handling). Update the conditional branch where mode === 'idle' inside
handleOrbClick to call invoke('activate_main_window') only if isTauri() returns
true, otherwise skip the invoke (or use the non-Tauri fallback if needed).

Comment on lines +446 to +450
/** Record mouse-down position; defer drag until the pointer actually moves. */
const handleDragStart = useCallback((e: React.MouseEvent) => {
if (e.button !== 0) return;
pressRef.current = { x: e.screenX, y: e.screenY, dragStarted: false };
}, []);
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 | 🟠 Major

Cancel any pending single-click when a new press starts.

A click scheduled on the previous mouseup can still fire after the next mousedown. If the user starts a drag within the 250ms debounce window, handleOrbClick() can run mid-drag and break the preserved drag-to-reposition flow.

Suggested fix
   const handleDragStart = useCallback((e: React.MouseEvent) => {
     if (e.button !== 0) return;
+    if (clickTimerRef.current !== null) {
+      window.clearTimeout(clickTimerRef.current);
+      clickTimerRef.current = null;
+    }
     pressRef.current = { x: e.screenX, y: e.screenY, dragStarted: false };
   }, []);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/** Record mouse-down position; defer drag until the pointer actually moves. */
const handleDragStart = useCallback((e: React.MouseEvent) => {
if (e.button !== 0) return;
pressRef.current = { x: e.screenX, y: e.screenY, dragStarted: false };
}, []);
/** Record mouse-down position; defer drag until the pointer actually moves. */
const handleDragStart = useCallback((e: React.MouseEvent) => {
if (e.button !== 0) return;
if (clickTimerRef.current !== null) {
window.clearTimeout(clickTimerRef.current);
clickTimerRef.current = null;
}
pressRef.current = { x: e.screenX, y: e.screenY, dragStarted: false };
}, []);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/overlay/OverlayApp.tsx` around lines 446 - 450, handleDragStart
currently records pressRef but doesn't cancel any pending single-click timer, so
a click scheduled on the previous mouseup can still fire mid-drag; update
handleDragStart to also clear the pending click timeout (e.g., call clearTimeout
on your single-click timer ref such as singleClickTimeoutRef or clickTimeoutRef
and set it to null) when a new mousedown begins, so handleOrbClick cannot run
during a subsequent drag; keep the existing e.button check and pressRef update.

…show-window

# Conflicts:
#	app/src-tauri/src/lib.rs
@senamakel senamakel merged commit 5082f3e into tinyhumansai:main Apr 17, 2026
8 checks passed
AusAgentSmith pushed a commit to AusAgentSmith/openhuman that referenced this pull request May 23, 2026
…inyhumansai#611)

* feat(tauri): add activate_main_window command for overlay (tinyhumansai#605)

Exposes the existing show_main_window helper as a Tauri command so the
overlay webview can bring the main window to front. The command is
whitelisted in allow-core-process.toml so the overlay window capability
can invoke it.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(overlay): activate main window on orb click (tinyhumansai#605)

The overlay orb had no click behavior. Now clicking it in idle mode
invokes activate_main_window, bringing the main app window to the front
(mirrors the tray icon flow).

Since the overlay is an NSPanel NonactivatingPanel on macOS, React's
synthesized onClick does not fire. Instead we record the press position
on mousedown and emulate click on mouseup when the pointer stayed within
a 4px slop. Dragging is deferred to mousemove past the slop so startDragging
doesn't swallow the mouseup event.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(window): propagate show_main_window errors instead of swallowing them (tinyhumansai#605)

`show_main_window` silently logged failures and returned `()`, so the
`activate_main_window` Tauri command could report success on a no-op. Thread
`Result<(), String>` through so JS `invoke().catch()` sees real failures, and
preserve the previous log-on-error behavior at the tray/Reopen call sites
where no caller consumes the result.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(overlay): stabilize orb click vs drag vs double-click (tinyhumansai#605)

Two follow-ups to the deferred-drag pattern:

1. Drop stale pressRef when the primary button is no longer held during
   mousemove. Window-drag / focus changes can steal the mouseup, leaving
   the ref populated so the next idle hover would start a spurious drag.

2. Debounce the synthetic click by 250 ms so a follow-up dblclick can
   cancel it — the double-click-to-reset gesture was firing activate +
   reset together. Clear the timer on dblclick and on unmount.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
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.

[Feature] Clicking the overlay orb should activate/bring the app window to front

2 participants