Phase 15: Desktop application
Implemented
- Added a pinned Tauri 2/Rust native shell and generated platform icon assets from the Switchyard mark.
- Bundled the trimmed Go binary as a target-triple sidecar with exact version, commit, and build-time identity.
- Added a typed, versioned
desktop snapshotCLI boundary for daemon, host, project runtime/health, workspace, recent-operation, and port-conflict state. - Added exact desktop/bundled-sidecar and compatible same-major daemon/API/ minimum-schema preflight before browser credential issuance or native mutations.
- Added a migration rollback guard: a binary refuses a database newer than its embedded migrations before Goose attempts an upgrade.
- Added a live tray with daemon state and bounded project/workspace open, start, and stop actions; close-to-tray preference; optional launch-at-login; single-instance focus; and project/workspace deep links.
- Added transition-based native notifications for failed/partial operations, project health, daemon connectivity, Docker connectivity, port conflicts, and high host resource use.
- Added a release-only, public-key-verified updater and a protected GitHub workflow for signed, notarized, draft macOS releases.
- Added macOS desktop CI, source-build/headless installation guidance, and explicit uninstall/data-retention choices.
Architecture decisions
No ADR changed. The implementation applies ADR-0003’s bundled Go control plane, ADR-0009’s thin Tauri adapter, ADR-0013’s authenticated loopback boundary, and ADR-0015’s macOS-first platform order. Rust contains native presentation and fixed command mappings only; all product behavior remains behind Go application services.
Safety properties
- The Vue loopback origin has no Tauri capabilities. Native plugins are called only by Rust, and no arbitrary shell command is exposed.
- Deep links, UI routes, resource identifiers, sidecar envelopes, response size, version, API, and database schema are validated before use.
- Raw sidecar stderr and repository/log payloads are never copied into desktop notifications.
- First observation does not replay old failures. Health, daemon, and resource notices are edge-triggered rather than emitted every poll.
- Closing or uninstalling the adapter never deletes state, repositories, containers, volumes, or logs. Newer databases fail closed under older code.
- Release builds require updater and Apple signing material from a protected CI environment; local debug builds have no update authority.
Tests added
- Database-newer-than-binary rejection before migration.
- Local UI path validation for remote origins, network paths, dot/encoded-dot traversal, fragments, backslashes, and injected bootstrap credentials.
- Exact desktop/sidecar plus same-major daemon/API/minimum-schema compatibility acceptance, additive-schema acceptance, and fail-closed mismatch cases.
- Deep-link route parsing and rejection of traversal, foreign origins, query injection, and malformed identifiers.
- Sidecar resource-identifier containment and versioned command response validation.
- Notification initialization, operation/health transitions, warning deduplication, and recovery.
Acceptance criteria status
- Desktop behavior is the same Go application behavior used by browser, CLI, and MCP clients.
- Closing the window follows the persisted user preference and never fabricates daemon shutdown.
- The tray reflects daemon/project/workspace state and uses fixed typed actions.
- Incompatible sidecar/daemon/API/minimum-schema combinations are detected before native mutation; newer databases are also rejected by Go before migration.
- Installation supports signed/notarized macOS releases and a CLI-only headless path.
- Uninstall guidance offers preserve, backup, and reviewed-delete data choices without implying repository or Docker-volume deletion.
Verification evidence
Focused verification on 2026-07-16 passed:
- the Go SQLite, CLI, and HTTP-client packages passed their focused tests;
- six Rust unit tests passed and Clippy with
-D warningswas clean; - the bundled sidecar reported version
0.1.0-alpha.0and the current commit; - Tauri built a native x64
Switchyard.appand 24 MiB DMG;hdiutil verifyreported a valid checksum; - an ad-hoc signature passed
codesign --verify --deep --strict; - the packaged app launched a visible 1440×960
Switchyardwindow and started its bundled detached daemon under the expected per-user data directory; - clicking the standard close control removed the window while the desktop process remained alive, and a second launch restored the same instance;
- the app’s Info.plist contains the
switchyardURL scheme; deep-link parsing and route selection passed unit tests; and - quitting the adapter left the independent daemon running as documented.
The complete repository make quality run passed: generated artifacts were
clean; Go formatting, vet, GolangCI-Lint, architecture checks, the full suite,
race detection, migration checks, and Govulncheck passed; all 20 Vue unit tests,
four browser E2E tests, and nine visual tests passed; all six Rust tests,
rustfmt, and Clippy with -D warnings passed; and the production Vue/Go build,
native application, and DMG were produced. Apple notarization itself is
credential-gated and is verified by the protected release workflow rather than
a local debug build.
Known limitations and deferred work
- Phase 15 packaged macOS first. Phase 18 now supplies the Linux/Windows bundle matrix, native Windows adapters, and explicit WSL behavior.
- Signing, notarization, and production updater-feed access require external Apple and Minisign credentials and cannot be truthfully exercised by an unsigned local build.
- The daemon intentionally outlives the desktop adapter so CLI, browser, MCP, and detached terminal sessions remain available.