Spotuify macOS App

Spotuify macOS app

Native SwiftUI client for Spotuify. It exists for the times a terminal TUI
is the wrong surface: menubar controls, a normal macOS window, system media
integration, and a friendlier first impression for people who still want the
daemon-backed product.

What it is

The app lives in clients/macos/. It is not a second backend. SpotuifyKit
speaks the same daemon IPC contract as the CLI and TUI, renders daemon state,
and sends daemon requests. Playback still belongs to the Rust daemon and
embedded librespot.

Line to keep: the macOS app is another client of the core, not a fork of the
product.

Current shipped surface

Validated against clients/macos/ on 2026-08-12:

The app has grown beyond a menubar remote, but its reason for existing has not
changed. It is the native macOS view of the same local music service.

Why it exists

The terminal is the canonical surface, but a shipped music product eventually
needs a calmer daily-use client:

The app should make Spotuify easier to live with without weakening the
CLI-first contract. If a capability only works in the app, that is still a
spotuify product bug.

Packaging truth

The DMG is built by clients/macos/scripts/build-dmg.sh.

Current code truth:

The tag-driven CI release does not build this DMG today. It ships CLI archives
and Homebrew. That means the DMG is a manual release artifact unless the release
workflow changes.

Current risk

The easy docs mistake is to talk about "the macOS release" as one thing. It is
at least two surfaces:

Those are both valid if the docs name them honestly. They become broken
promises when the install page says "latest signed build" while the workflow
cannot produce that build.

The distinction was rechecked on v0.1.102. The CLI workflow contains guarded
Developer ID and notarization steps, but both macOS jobs logged that signing
secrets were missing and shipped unsigned archives. A green optional step proves
that the workflow completed, not that the artifact was signed.

Contract lesson

The macOS client makes Client contract drift is product drift concrete.
Matching type names in Swift and Rust are not enough. The protocol version
gate, request-kind fixture, provider-policy fixture, wire-decoding tests, and
live-daemon suite are what keep "another client" true as the daemon changes.