Previously, GatewayLaunchAgentManager.set() returned early for ANY launchd change
in remote mode. This prevented the launchd job from being unloaded when switching
from local to remote mode, causing a crash-loop as the job kept trying to bind
to port 18789 (which the SSH tunnel was using).
Now only 'enable' is blocked in remote mode; 'disable' (uninstall) proceeds
normally so the job is properly unloaded.
Fixes#2354
- Fix "Test Remote" button failing for non-standard SSH ports (e.g., user@host:2222)
The raw target string was passed directly to SSH, which interpreted host:2222
as the hostname. Now uses -p flag for the port, matching RemotePortTunnel.swift.
- Fix SSH target field auto-repopulating after being cleared
The config file watcher would reconstruct the SSH target from gateway.remote.url,
undoing user edits. Removed auto-population from applyConfigOverrides(); initial
population still happens in init() for first-time setup only.
- Document gateway.remote.sshTarget and gateway.remote.sshIdentity config keys
- Add non-standard SSH port example to docs
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Fixed issue where trigger words would disappear when typing or when adding new trigger words. The problem was that `swabbleTriggerWords` changes were triggering `VoiceWakeRuntime.refresh()` which sanitized the array by removing empty strings in real-time.
Solution: Introduced local `@State` buffer `triggerEntries` with stable UUID identifiers for each trigger word entry. User edits now only affect the local state buffer and are synced back to `AppState` on explicit actions (submit, remove, disappear). This prevents premature sanitization during editing.
The local state is loaded on view appear and when the view becomes active, ensuring it stays in sync with `AppState`.
Add Mattermost as a supported messaging channel with bot API and WebSocket integration. Includes channel state tracking (tint, summary, details), multi-account support, and delivery target routing. Update documentation and tests to include Mattermost alongside existing channels.