# Dashboard window Dock/taskbar/window-switching research

Date: 2026-05-14
Branch: `analysis/dashboard-window-dock-taskbar-817`
Worktree: `/Users/lonng/devel/pingcap/agentteam/.worktrees/dashboard-window-dock-taskbar-817`

## Question

The dashboard/window opened from the desktop tray/menu currently behaves like a tray accessory window: it does not expose a normal Dock/taskbar icon, and the user cannot reliably reach it through OS window switching. Research how to make it show as a normal app/window when opened, or otherwise make it reachable by keyboard switching.

## Current implementation map

- Tray/Wails app entrypoint: `desktop/internal/daemon/runner_tray.go`
  - Creates the Wails application with `Mac.ActivationPolicy: application.ActivationPolicyAccessory`.
  - This is the primary macOS reason the daemon is an accessory/tray app rather than a regular Dock app.
- macOS launcher: `desktop/installer/mac/AgentTeamLauncher.m`
  - The outer launcher also sets `NSApplicationActivationPolicyAccessory`, but it immediately starts the managed `agentteam app-launch` process and exits. The long-lived behavior is controlled by the Wails daemon process above.
- Window creation and tray menu actions: `desktop/internal/daemon/window_manager.go`
  - `BuildTrayMenu` wires Dashboard to `CmdOrCtrl+0` and calls `m.ShowWindow(windowKindDashboard, "")`.
  - `showWindowTargetSync` calls `m.app.Show()`, `rec.window.Show()`, and `rec.window.Focus()`.
  - `newManagedWindowSync` creates a normal Wails `WebviewWindow` but uses macOS hidden titlebar styling and Windows frameless styling.
- Windows-specific window options in the same file:
  - `Windows.DisableIcon: true` removes/disables the window icon.
  - `Windows.HiddenOnTaskbar` is not set, so Wails defaults to putting the window in the taskbar (`WS_EX_APPWINDOW`) unless an override is introduced elsewhere.

## Platform/API findings

### macOS

- Wails v3 `MacOptions.ActivationPolicy` maps directly to Cocoa activation policy. Wails defines:
  - `ActivationPolicyRegular`: UI app.
  - `ActivationPolicyAccessory`: background/accessory/tray-style app.
- Current code explicitly uses `ActivationPolicyAccessory`, so the daemon intentionally does not appear as a normal Dock/Cmd-Tab app.
- `m.app.Show()` only un-hides the app; it does not change activation policy. Therefore the current `ShowWindow` path cannot make the Dock icon appear by itself.
- Wails has a dock service (`pkg/services/dock`) with `ShowAppIcon`/`HideAppIcon`, implemented by changing `NSApp` activation policy. However the current Wails dock service uses a synchronous dispatch to the main queue; calling it from inside `application.InvokeSync` code paths would risk deadlock. If dynamic Dock visibility is implemented, prefer a small local darwin helper that handles “already on main thread” safely, or call Wails dock service only from a non-main-thread path.
- Wails `MacWindow.CollectionBehavior` supports `MacWindowCollectionBehaviorParticipatesInCycle`, documented in source as including a window in Cmd+` cycling. Current window options do not set it; Wails maps zero to `FullScreenPrimary`. For robust macOS window cycling, set collection behavior explicitly.

### Windows

- Wails `WindowsWindow.HiddenOnTaskbar` defaults to false. In Wails alpha.74 that yields `WS_EX_APPWINDOW`, i.e. taskbar-visible behavior.
- If `HiddenOnTaskbar` is true, Wails switches toward `WS_EX_NOACTIVATE`, which prevents normal activation/switch behavior. Current code does not set this.
- Current code sets `DisableIcon: true`, and Wails responds by disabling the native icon (`WS_EX_DLGMODALFRAME`). If the complaint is “taskbar button exists but has no icon / cannot identify it”, this is the likely Windows-side fix: stop disabling the icon and ensure `Icon: appIconBytes` remains set in `application.Options`.
- If the complaint is “not in Alt-Tab/taskbar at all” on Windows, verify native extended style at runtime; based on current code and Wails source, it should be taskbar-visible unless another launcher/service context or style override hides it.

## Options

### Option A — Always regular desktop app

Change `desktop/internal/daemon/runner_tray.go` on macOS from `ActivationPolicyAccessory` to `ActivationPolicyRegular`.

Pros:
- Smallest change.
- Dock icon and Cmd-Tab should work whenever the daemon is running.
- Least state-management complexity.

Cons:
- Dock icon appears even when all windows are hidden and the daemon is only running as a tray service.
- Changes product feel from tray utility to normal desktop app.

### Option B — Dynamic Dock/taskbar presence only while windows are visible (recommended)

Keep tray daemon as accessory by default, but promote it to regular app when showing a managed window, and demote it back to accessory when the last managed window is hidden/destroyed.

Implementation shape:
- Add a `desktopWindowVisibilityController` / platform helper under `desktop/internal/daemon/`.
- On macOS:
  - `ShowWindow` / `OpenWorkspaceWindow` calls `setActivationPolicyRegular()` before showing/focusing the window.
  - Close/hide paths call `maybeSetActivationPolicyAccessory()` only after confirming no managed window remains visible.
  - The helper must be main-thread safe and should not deadlock inside `application.InvokeSync`.
- On Windows:
  - Leave `HiddenOnTaskbar` false.
  - Change `Windows.DisableIcon` from `true` to `false` so taskbar/titlebar icon is available.
- For macOS window cycling:
  - Set `Mac.CollectionBehavior` to include `MacWindowCollectionBehaviorParticipatesInCycle` plus the desired fullscreen behavior.

Pros:
- Matches user expectation: when a real window is open, it behaves like a normal window; when hidden, AgentTeam stays a quiet tray daemon.
- Keeps current tray-first design.
- Works for dashboard and future managed windows through a single manager boundary.

Cons:
- Requires careful accounting of visible windows.
- Needs macOS-specific helper/tests to avoid main-thread deadlocks.
- Possible Dock icon “blink” when windows open/close.

### Option C — Keep no Dock/taskbar icon, add only shortcuts

Use tray/menu accelerators or a global hotkey to show/focus the dashboard.

Pros:
- Avoids product change to Dock/taskbar visibility.
- Minimal visual impact.

Cons:
- Does not satisfy “show in Dock/taskbar”.
- OS switchers still cannot reach the window if the app remains accessory/no-activate.
- Global hotkeys have conflict/permission concerns and still feel unlike a normal window.

## Recommendation

Choose **Option B**.

It preserves AgentTeam’s tray-daemon identity but makes open dashboard/workspace windows behave like normal windows. The implementation should be centralized in `desktopWindowManager`, not scattered through tray callbacks, because all native window lifecycle events already pass through that manager.

Minimum acceptance criteria:

1. macOS, no visible AgentTeam windows: no Dock/Cmd-Tab item.
2. macOS, open Dashboard from tray: Dock icon appears, app can be selected with Cmd-Tab, and Dashboard is focused.
3. macOS, multiple AgentTeam windows: Cmd+` cycles through visible AgentTeam windows.
4. macOS, close/hide the last visible AgentTeam window: Dock icon disappears, tray remains.
5. Windows, open Dashboard from tray: taskbar button has AgentTeam icon and can be reached with Alt-Tab/taskbar.
6. Existing tray shortcuts (`CmdOrCtrl+0`, workspace `CmdOrCtrl+1..9`, settings `CmdOrCtrl+,`) still work as menu accelerators.

## Suggested implementation files

- `desktop/internal/daemon/runner_tray.go`
  - Keep default `ActivationPolicyAccessory`; optionally inject/initialize visibility controller.
- `desktop/internal/daemon/window_manager.go`
  - Call visibility controller from show/close/hide/destroy paths.
  - Set explicit macOS `CollectionBehavior` on new windows.
  - Set `Windows.DisableIcon: false`.
- New files:
  - `desktop/internal/daemon/window_activation_darwin.go`
  - `desktop/internal/daemon/window_activation_other.go`
  - Unit tests around visibility accounting in `window_manager_test.go`; manual QA for Dock/taskbar behavior.

## References

- Local Wails version: `github.com/wailsapp/wails/v3 v3.0.0-alpha.74` from `desktop/go.mod`.
- Wails source inspected locally:
  - `pkg/application/application_options.go`
  - `pkg/application/webview_window_options.go`
  - `pkg/application/webview_window_darwin.go`
  - `pkg/application/webview_window_windows.go`
  - `pkg/services/dock/dock_darwin.go`
- Official docs consulted:
  - Apple AppKit `NSApplication.ActivationPolicy`: https://developer.apple.com/documentation/appkit/nsapplication/activationpolicy-swift.enum/prohibited
  - Microsoft extended window styles: https://learn.microsoft.com/en-us/windows/win32/winmsg/extended-window-styles
  - Wails package docs: https://pkg.go.dev/github.com/wailsapp/wails/v3@v3.0.0-alpha.74/pkg/application

## Implementation note

Implemented Option B in this branch. Automated tests cover switcher-safe Wails window options and activation demotion accounting. Manual QA still required on a packaged macOS app and Windows app because Dock/Cmd-Tab/taskbar/Alt-Tab behavior depends on native window-manager state.
