Skip to content

fix: use setActivationPolicy for proper Dock behavior#113

Merged
grimmerk merged 3 commits intomainfrom
fix/dock-activation-policy
Apr 6, 2026
Merged

fix: use setActivationPolicy for proper Dock behavior#113
grimmerk merged 3 commits intomainfrom
fix/dock-activation-policy

Conversation

@grimmerk
Copy link
Copy Markdown
Owner

@grimmerk grimmerk commented Apr 6, 2026

Summary

Replace app.dock.hide()/show() with app.setActivationPolicy() for proper Dock behavior in Normal App mode.

Before (app.dock.hide/show)

  • Normal mode: Dock icon appears but no running dot, shows on right side
  • Menu bar mode: dock.hide() only removes dot, icon may persist if manually pinned
  • Known Electron bugs with dock.hide() (breaks app.hide(), conflicts with setVisibleOnAllWorkspaces)

After (setActivationPolicy)

  • Normal mode: 'regular' — proper Dock icon with running dot, App Switcher visible
  • Menu bar mode: 'accessory' — no Dock icon at all (same as LSUIElement)
  • LSUIElement=true kept in Info.plist to prevent Dock icon flash on app launch

Caveat

  • Switching to 'accessory' hides all windows — re-shown with 100ms delay
  • setActivationPolicy is a standard macOS API (NSApplicationActivationPolicy), not a hack

Tray menu mode toggle

  • Right-click tray icon → "Switch to Menu Bar Mode" / "Switch to Normal App Mode"
  • Uses same logic as Settings toggle (IPC handler)

Test plan

  • Normal mode: Dock icon with running dot (left side)
  • Normal mode: visible in App Switcher (Cmd+Tab)
  • Menu bar mode: no Dock icon
  • Switch Normal → Menu Bar: icon disappears, window re-shows
  • Switch Menu Bar → Normal: icon appears with dot
  • App launch in Normal mode: no Dock icon flash
  • Tray right-click → mode toggle works in both directions

🤖 On behalf of @grimmerk — generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Resolved dock icon flashing issue: eliminated unwanted Dock icon behavior on application launch when operating in Menu Bar mode, providing a cleaner startup experience
  • New Features

    • Added application mode toggle to the tray context menu, allowing users to easily switch between Normal Application Mode and Menu Bar Mode without requiring an application restart

Replace app.dock.hide()/show() with app.setActivationPolicy():
- Normal mode: 'regular' (dock icon + running dot + App Switcher)
- Menu bar mode: 'accessory' (no dock icon, same as LSUIElement)

setActivationPolicy is the official macOS API for this, cleaner
than dock.hide() which has known Electron bugs. LSUIElement=true
kept in Info.plist to prevent dock icon flash on app launch.

Caveat: switching to 'accessory' hides all windows — re-show
with 100ms delay after policy change.

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

coderabbitai bot commented Apr 6, 2026

📝 Walkthrough

Walkthrough

Version 1.0.71 release updates macOS dock behavior to use app activation policy instead of hide/show methods, and adds a tray context menu item to toggle between Normal and Menu Bar application modes.

Changes

Cohort / File(s) Summary
Version & Release Management
CHANGELOG.md, package.json
Version bumped to 1.0.71 with two changelog entries: dock/menu-bar behavior changes via activation policy and new tray mode toggle feature.
Tray Menu Integration
src/TrayGenerator.ts
Added onToggleAppMode and getAppMode callbacks; tray right-click context menu now includes dynamic mode-toggle menu item at the top (before Settings), with label determined by current mode.
App Mode Control & Activation Policy
src/main.ts
Replaced app.dock.hide/show calls with app.setActivationPolicy() for mode switching; integrated tray callbacks for mode toggling; updated IPC handler to manage dock visibility via activation policy with 100ms delay for accessory mode window re-display.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant Tray as Tray Menu
    participant Main as Main Process
    participant IPC as IPC Channel
    participant App as Electron App

    User->>Tray: Click mode toggle item
    Tray->>Main: Call onToggleAppMode()
    Main->>Main: Compute opposite mode
    Main->>IPC: Emit 'set-app-mode' event
    IPC->>App: Handle set-app-mode
    
    alt Mode = 'menubar'
        App->>App: setActivationPolicy('accessory')
        App->>App: Hide all windows
        App->>App: Wait 100ms
        App->>App: Re-show switcher window
    else Mode = 'normal'
        App->>App: setActivationPolicy('regular')
    end
    
    App-->>User: App mode updated
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐰 The tray now hops with purpose bright,
Toggling modes from day to night,
No more dock dances, old and worn—
Activation policies greet the morn! ✨
Menu Bar or Normal, rabbit's choice—
The tray menu gives the app a voice! 🎯

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: replacing dock.hide()/show() with setActivationPolicy() for proper Dock behavior, which is the core fix across multiple files.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ 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/dock-activation-policy

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

grimmerk and others added 2 commits April 7, 2026 01:29
Shows 'Switch to Menu Bar Mode' or 'Switch to Normal App Mode'
at the top of the tray context menu. Reuses the same IPC logic
as the Settings toggle.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@grimmerk
Copy link
Copy Markdown
Owner Author

grimmerk commented Apr 6, 2026

@cubic-dev-ai review
@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 6, 2026

@grimmerk Sure, I'll review the changes in this PR!


🧠 Learnings used
Learnt from: CR
Repo: grimmerk/codev PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-04-06T13:05:55.994Z
Learning: Wait for all AI reviewers (CodeRabbit and cubic) to finish before pushing fixes to avoid redundant review cycles and conserve review quota
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@cubic-dev-ai
Copy link
Copy Markdown

cubic-dev-ai bot commented Apr 6, 2026

@cubic-dev-ai review
@coderabbitai review

@grimmerk I have started the AI code review. It will take a few minutes to complete.

Copy link
Copy Markdown

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

Choose a reason for hiding this comment

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

No issues found across 4 files

Copy link
Copy Markdown

@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.

🧹 Nitpick comments (1)
src/main.ts (1)

1983-1991: Add a guard for destroyed window in the setTimeout callback.

The win reference is captured before the 100ms delay, but the window could be destroyed during that time (e.g., user quits the app). While unlikely in practice, adding a defensive check would prevent potential errors.

🛡️ Suggested fix
     app.setActivationPolicy('accessory');
     // accessory mode hides all windows — re-show after short delay
     const win = getSwitcherWindow();
     if (win) {
-      setTimeout(() => { win.show(); win.focus(); }, 100);
+      setTimeout(() => {
+        if (win && !win.isDestroyed()) {
+          win.show();
+          win.focus();
+        }
+      }, 100);
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main.ts` around lines 1983 - 1991, The delayed callback captures the
BrowserWindow reference `win` but doesn't check if it was destroyed during the
100ms delay; update the setTimeout callback that currently calls `win.show()`
and `win.focus()` to first verify the window still exists and is not destroyed
(e.g., check `win` is truthy and `!win.isDestroyed()` or equivalent) before
calling show/focus; apply this guard in the code path where
`getSwitcherWindow()` is used so the callback safely no-ops if the window was
closed.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/main.ts`:
- Around line 1983-1991: The delayed callback captures the BrowserWindow
reference `win` but doesn't check if it was destroyed during the 100ms delay;
update the setTimeout callback that currently calls `win.show()` and
`win.focus()` to first verify the window still exists and is not destroyed
(e.g., check `win` is truthy and `!win.isDestroyed()` or equivalent) before
calling show/focus; apply this guard in the code path where
`getSwitcherWindow()` is used so the callback safely no-ops if the window was
closed.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d1d03344-8f53-4146-96f0-3e502b7b3776

📥 Commits

Reviewing files that changed from the base of the PR and between 20908f9 and 8693a7b.

📒 Files selected for processing (4)
  • CHANGELOG.md
  • package.json
  • src/TrayGenerator.ts
  • src/main.ts

@grimmerk grimmerk marked this pull request as ready for review April 6, 2026 17:40
@grimmerk grimmerk merged commit 7272680 into main Apr 6, 2026
2 checks passed
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