Skip to content

Architecture

One per-user daemon owns all mutable runtime state. It resolves the effective theme, optionally reconciles host appearance, runs configured hooks, and broadcasts changes. The CLI, tray, Neovim plugin, and external integrations are clients of that source of truth rather than independent OS detectors.

The daemon serializes mutations through one central Tokio event loop. Its inputs are:

  1. platform appearance updates;
  2. scheduled sun refreshes and wake guards;
  3. configuration-file notifications;
  4. IPC commands;
  5. SIGTERM or Ctrl-C shutdown.
  1. Load and validate configuration, using defaults if the file is absent.
  2. Read system appearance and compute sun state.
  3. Resolve and apply the initial effective theme.
  4. Run the optional startup hook once.
  5. Bind local IPC and watch the configuration file.
  6. Broadcast daemon_ready.

A mode update emits theme_changed if the effective theme changes. If the control mode changes but the effective value does not, it emits mode_changed. A successful configuration reload emits config_changed. Subscribers receive events through a bounded Tokio broadcast channel.

The tray and Neovim integrations stay thin. The tray owns its native GUI loop and delegates state to the daemon over IPC. Neovim obtains an initial status, follows the NDJSON subscription, and applies only editor-local appearance. Neither client duplicates resolution.

Path Responsibility
crates/theme-core/src/theme.rs Theme, Mode, and Source domain types.
crates/theme-core/src/state.rs Serializable daemon state and pure resolver.
crates/theme-core/src/config.rs TOML schema, defaults, validation, load/save, and platform paths.
crates/theme-core/src/sun.rs Offline solar calculations and next-transition scheduling.
crates/theme-core/src/protocol.rs IPC request, response, and error wire types.
crates/theme-core/src/events.rs Subscription event wire types.
crates/theme-agent/src/main.rs Process entry point and main-thread platform constraints.
crates/theme-agent/src/daemon/mod.rs Runtime state owner and central event loop.
crates/theme-agent/src/ipc/ Unix socket and Windows named-pipe client/server.
crates/theme-agent/src/platform/ macOS, Linux, Windows, and mock backends.
crates/theme-agent/src/cli/ Clap surface, output, config editing, and IPC commands.
crates/theme-agent/src/service.rs User service and tray-autostart lifecycle.
crates/theme-agent/src/tray/mod.rs Native tray UI and IPC worker.
lua/theme_agent/, plugin/ Neovim client.
crates/theme-agent/tests/daemon_cli.rs Real-binary daemon, CLI, and IPC integration tests.
xtask/ Workspace version checks and updates.
justfile Supported development, CI, install, and service recipes.

theme-core remains synchronous and platform-neutral. It owns pure state transitions and serialized contracts, not I/O. Runtime I/O, async orchestration, OS APIs, service management, and UI code belong in theme-agent.

This boundary keeps the resolver exhaustive and deterministic while platform integration remains testable through native and mock backends. See Development for the verification model.