# Desktop Tray Client > **Platform:** the tray currently ships for **macOS only**. The design is portable — `fyne.io/systray` supports Linux (appindicator/GTK) and Windows — but the built and tested binary is the macOS menu-bar app. The Linux/Windows behaviour described below (e.g. the **Open folder** file-manager calls) is aspirational until a native build for those platforms is exercised. ## Overview `dropbear-tray` is a menu-bar/tray client for the dropbear sync daemon. It runs as a separate native binary and provides an always-present status icon that reflects whether your synced folders are up to date, plus quick actions for starting or stopping the daemon, triggering a sync, opening the web dashboard, or revealing a root folder. ## Architecture The tray is a **thin client**: it holds no sync state and performs no object-storage, manifest, or local-state I/O. All displayed information comes from the daemon's loopback status API (`GET /v1/status`). The actions it takes are requesting a manual sync (`POST /v1/roots/{id}/sync`) and starting/stopping the daemon through the OS service manager (see [Starting and Stopping the Daemon](#starting-and-stopping-the-daemon)); it never authors service files itself. If the tray crashes, sync is unaffected. ## Discovery and Authentication On startup and on each daemon-down → reachable transition, the tray reads the runtime handoff file at `$XDG_CONFIG_HOME/dropbear/runtime/daemon.json` (or `$HOME/Library/Application Support/dropbear/runtime/daemon.json` on macOS). The file contains `{pid, url, token, started_at}`. The tray verifies the recorded PID is alive, then uses `url` + `token` for all API calls as `Authorization: Bearer `. A missing file, dead PID, or connection-refused response maps to the "daemon down" state — no error dialog is shown. This is the same discovery path used by `dropbear daemon status`, factored into `internal/daemonclient`. ## Icon States The menu-bar icon is the dropbear logo with a status badge composited into the corner. The badge — not the logo — carries the state, since at menu-bar size (~22px) the badge is what stays legible: | Badge | Meaning | | --------------- | -------------------------------------------------------------- | | Green check | All roots online, no errors, no conflicts | | Blue refresh | Any root's last tick is in flight, or a manual sync is pending | | Red exclamation | Any root offline, has `last_error`, or has conflict counters | | Grey cross | Daemon unreachable | Precedence: **down > attention > syncing > idle**. The badge carries the sync sub-state (idle/syncing/attention); the top menu line instead shows the daemon's lifecycle state (`Dropbear Daemon: running` / `stopped`) and doubles as the start/stop toggle described below. ## Menu Actions - **Dropbear Daemon: running / stopped** — the top line is the daemon start/stop toggle. Clicking it flips the daemon on or off via the OS service manager. See [Starting and Stopping the Daemon](#starting-and-stopping-the-daemon). - **Open dashboard** — opens the daemon's embedded web UI (`url` from the runtime file) in the default browser. Available when daemon is reachable. - **Open folder** — reveals a root's local path in the OS file manager (`open -R` on macOS, `xdg-open` on Linux, Explorer on Windows). - **Sync now** — posts `POST /v1/roots/{id}/sync`, reads the returned `tick_id`, shows the syncing icon, and polls until that tick completes. - **Quit** — exits the tray process only. The daemon keeps running and syncing. Always available, and pinned to the bottom of the menu. Each root appears as ` ()`, grouped with **Open dashboard** in one section. The tray polls `GET /v1/status` every 2s; the extra polls are an immediate one after a "Sync now" returns and one after a start/stop toggle. ## Starting and Stopping the Daemon The top menu line — `Dropbear Daemon: running` / `stopped` — is also the start/stop toggle. Clicking it while the daemon is running stops it; clicking while it is stopped starts it. This drives the platform service manager through `internal/daemonctl`: - **macOS (launchd):** stop = `launchctl disable` + `bootout`; start = `launchctl enable` then `bootstrap` (or `kickstart -k` if already loaded). - **Linux (systemd `--user`):** stop = `systemctl --user disable` + `stop`; start = `systemctl --user enable` + `restart`. - **Windows / other:** unsupported — the click is logged and otherwise ignored. **Off means off.** Stop is *persistent*: it disables the service so it stays down across logouts and reboots, not just for the current session. A bare stop would be undone at the next login by launchd's `RunAtLoad` or systemd's enabled symlink, so the toggle would silently pop back on; `disable` is what prevents that. Start re-enables and relaunches. On macOS you can confirm the persistent state without rebooting via `launchctl print-disabled gui/$(id -u)` (the disabled bit) and `launchctl print gui/$(id -u)/net.tfks.dropbear` (whether it is loaded right now). **Requires an installed service.** The daemon must already be installed (`dropbear daemon install`). If it isn't, the toggle logs guidance to run that command rather than installing anything itself — the tray stays a thin client, and install stays the one code path shared with headless/server setups. **In-flight behaviour.** On click the line immediately shows the requested direction (`starting…` / `stopping…`) and disables itself until the next 2s status poll confirms the real state, so a slow service manager can't invite a double-click. If a start fails (or the daemon is still coming up), the next poll reconciles the line — a failed start reverts to `stopped`. A lifecycle error never crashes the tray: it is logged and left for the poll to resolve. ## Root Membership Is Frozen Per Session The tray builds its per-root menu entries **once**, from the first status poll that returns a non-empty roots array, and keeps that set fixed for the process lifetime. Later polls refresh only each entry's online/offline title and the aggregate icon — not the set of entries. Roots added to or removed from the daemon are **not** reflected until the tray is restarted. This is deliberate: `fyne.io/systray` is append-only with no insert/reorder API, and `AddSeparator()` returns no handle (separators can't be moved or removed). A fixed root set is what lets the entries sit in a stable section while **Quit** stays pinned to the bottom — Quit is re-pinned (with a divider above it) exactly once, at build time. Root membership is config-driven and changes rarely, and the thin-client design means a stale menu never affects sync correctness.