Preferences subprocess¶
This page mirrors implementation notes maintained in CLAUDE.md.
Update both when changing this subsystem.
The Preferences window is a separate subprocess launched via
platform.launch_settings(). It uses customtkinter and writes
config back to disk atomically; the parent observes the subprocess
exit and reloads its BrowserAuth cache + reconciles
streaming.enabled against a pre-launch snapshot.
Why a subprocess¶
- The menu-bar app's run loop (rumps on macOS, pystray on Windows) is single-threaded for UI. A long-running customtkinter window on the same loop would deadlock the menu/tray.
- Crash isolation — if a Preferences widget hits a Tk bug, the menu-bar app keeps running.
- IPC simplicity — there's none. The subprocess writes
config.json+browser_auth.jsonatomically; the parent reads them on subprocess exit. No pipes, no sockets, no shared-memory.
sequenceDiagram
participant Tray as Menu bar / tray
participant Sub as settings_window subprocess
participant Disk as config.json + browser_auth.json
Tray->>Sub: spawn (or focus existing window)
Sub->>Disk: read snapshot on launch
Sub->>Sub: user edits in-memory draft
Sub->>Disk: atomic write on Apply
Tray->>Tray: subprocess exits → reload BrowserAuth, reconcile streaming
Apply / Revert flow¶
Draft edits stay in memory until the user clicks Apply. The
footer Apply/Revert buttons enable only while
diff(draft, snapshot) is non-empty and every visible field
validates (regex compile, URL shape, etc.). See
settings_model for the validation logic.
Destructive actions — Clear Data, Reset Privacy Permissions, per-browser "Ask now" — bypass the draft and commit immediately. Those clicks are intent-to-act, not setting edits.
Multi-instance guard¶
The subprocess takes a file lock at <config root>/.cue.settings.lock
(fcntl on POSIX, msvcrt on Windows). A second launch exits silently
on lock contention. The parent process also tracks the live
subprocess PID and calls a platform-specific
focus_subprocess() helper to bring the existing window to the
front instead of spawning a new one.
Module split¶
| Module | Purpose |
|---|---|
cue.settings_model |
Pure-logic layer: load_draft() / validate_regex() / validate_api_key() / diff() / commit() / supported_browsers(). stdlib-only, unit-testable. No customtkinter, no Tk imports. |
cue.settings_window |
UI shell. customtkinter widgets, layout, the four tabs (General / Privacy / Data / About). Imports the model layer. |
The split exists so the validation rules and diff logic are covered by tests without spinning up a Tk root window.
Tab inventory¶
The four tabs and what they hold are walked through in
Preferences (user-facing). The internals
of those widgets live in cue.settings_window.
Menu consolidation rule¶
Top-level tray items are kept short on both platforms: Open Cue / Privacy paused / Enable Streaming / Preferences… / Quit Cue. New stateful settings go into Preferences tabs, not new tray menu items.
See also¶
cue.settings_model— module reference.- Preferences (user-facing) — tab-by-tab walkthrough.
- Architecture overview.