Tray App — Signet Docs

Docs / Infrastructure

Tray App

Desktop system tray companion for the Signet daemon.

Tray App

The Signet tray app is a lightweight desktop companion that sits in your system tray and shows the Daemon‘s current state. It provides quick controls — start, stop, restart, open Dashboard — without requiring a terminal or browser.

Source: packages/tray/

Architecture

The app is built with Tauri v2. Rust handles the tray lifecycle, menu rendering, and process management. A TypeScript polling loop drives state detection and communicates with the Rust layer via Tauri commands.

This split keeps the hot path (HTTP polling) in TypeScript and reserves Rust for platform-specific work (signal handling, PID file management, systemd interaction).

TypeScript side (src-ts/)

state.ts defines the DaemonState discriminated union:

  • unknown — initial state before the first poll
  • running — daemon is up; carries version, pid, uptime, healthScore, healthStatus
  • stopped — daemon is not running
  • error — poll failed unexpectedly; carries message

index.ts runs the polling loop. It polls /health every 5 seconds when the daemon is running, or every 2 seconds when stopped (for fast startup detection). Only calls the update_tray Tauri command when the state actually changes.

menu.ts translates DaemonState into a TrayUpdate struct passed to update_tray.

Rust side (src-tauri/src/)

Eleven Tauri commands are registered:

CommandDescription
start_daemonStart the daemon
stop_daemonStop the daemon (SIGTERM → 3s wait → cleanup)
restart_daemonStop then start with 500ms pause
get_daemon_pidRead the PID file
open_dashboardOpen http://localhost:3850 in the default browser
update_trayApply a new TrayState to the icon and menu
quick_captureOpen the quick-capture popup window
search_memoriesOpen the memory search popup window
quit_capture_windowClose the capture window
quit_search_windowClose the search window
quit_appExit the Tauri process

A DaemonManager platform trait abstracts start/stop/is_running. linux.rs is fully implemented. macOS and Windows are stubs.

Linux process management:

Start order: check for ~/.config/systemd/user/signet.service — if present, use systemctl --user start signet. Otherwise: locate bun binary → locate signet-daemon → fall back to the globally installed signet daemon start.

Stop: send SIGTERM, poll at 100ms intervals up to 3 seconds, then clean up the PID file.

StateMenu items
RunningOpen Dashboard · Stop · Restart · Quit
StoppedStart · Quit
ErrorRetry / View Logs · Quit

Icon States

Icon assets live at packages/tray/icons/. Three variants:

  • Running — full opacity
  • Stopped — gray / desaturated
  • Error — red accent

All icons are 32×32 PNG.

Build

Prerequisites

  • Bun — for building the TypeScript frontend
  • Rust toolchain (stable) — for compiling the Tauri backend
  • System libraries — Tauri v2 on Linux requires webkit2gtk-4.1, libayatana-appindicator3, and related GTK/GLib dev packages. On Arch: webkit2gtk-4.1 libayatana-appindicator. On Ubuntu/Debian: libwebkit2gtk-4.1-dev libappindicator3-dev.

Build from source

TypeScript is compiled with bun build --target browser (output to dist/). Tauri reads from dist/ as configured in tauri.conf.json. The Tauri build produces a self-contained .AppImage on Linux.

The tray build is independent of the monorepo root bun run build.

cd packages/tray

# 1. Install TS dependencies
bun install

# 2. Build TypeScript frontend (runs automatically as beforeBuildCommand)
bun run build:ts

# 3. Build the Tauri app
cargo tauri build

The build:ts script compiles src-ts/index.ts with --target browser --minify and copies the HTML entry points (index.html, capture.html, search.html) into dist/.

For development with hot-reload:

cd packages/tray
cargo tauri dev

This runs bun run build:ts as a beforeDevCommand and starts the Tauri dev server.

Output

PlatformOutput
Linux.AppImage in src-tauri/target/release/bundle/appimage/
macOS.dmg / .app in src-tauri/target/release/bundle/dmg/
Windows.msi in src-tauri/target/release/bundle/msi/

Configuration

The tray app currently has no user-facing configuration file. It hardcodes http://localhost:3850 as the daemon URL (matching the daemon’s default). If you change the daemon port via SIGNET_PORT, the tray app will not detect it — this is a known limitation.

The Tauri app metadata is defined in src-tauri/tauri.conf.json:

  • identifier: ai.signet.tray
  • productName: Signet Tray
  • bundle targets: all platforms enabled
  • CSP: allows connect-src to http://localhost:* for daemon API access

Polling Architecture

The TypeScript side runs four independent polling loops at staggered intervals:

PollerEndpointRunning intervalStopped interval
Health/health5s2s
Memories/api/memories?limit=1015s
Diagnostics/api/diagnostics30s
Embeddings/api/embeddings/status60s

Secondary pollers (memories, diagnostics, embeddings) only fire while the daemon is alive. When the daemon comes online, all secondary pollers kick off immediately. The tray icon and menu are only updated via invoke("update_tray") when the assembled state actually changes (JSON diff check).

The DaemonState union carries rich data when running: version, PID, uptime, health score, memory counts, embedding provider/model/coverage, queue depth, ingestion rate (exponential moving average), and the 10 most recent memories.

Known Limitations

  • Hardcoded daemon URL — the tray always connects to http://localhost:3850. Custom ports via SIGNET_PORT are not picked up.
  • macOS and Windows stubs — the DaemonManager trait only has a full implementation for Linux. macOS has an autostart helper but the start/stop commands are stubs. Windows is entirely unimplemented.
  • No autostart — the tray does not register itself to start on login (planned).
  • No desktop notifications — state transitions are only reflected in the tray icon/menu, not via OS notifications (planned).