Skip to content
WolfWave
Release notes

Changelog

Every WolfWave release. New features, fixes, and breaking changes. Auto-updating via Sparkle. macOS menu bar app for Apple Music streamers.

All notable changes to WolfWave are documented here. When an update is ready, the same notes show up right inside the app's update window.

v2.1.1 (Unreleased)

The next release. These ship on the Nightly channel off main until 2.1.1 is tagged.

Added

  • Five new Stream Deck keys. Announce Song posts what's playing to your chat, same wording as !song. Reject Request drops the request that's playing and tells chat it went, so the requester isn't left wondering (Skip just moves on silently). Block Requester bars the person who requested the current song, held for a second so you can't do it by accident. Request Access cycles who can request and shows the current setting on the key. Now Playing shows the track over its album art. See Stream Deck.
  • You can block people from requesting, not just songs. Settings → Song Requests → Blocklist now takes a Twitch username as well as song titles and artists. A blocked person's requests are turned away before WolfWave even looks the song up.
  • A Sponsor link you can turn off. WolfWave now has a Sponsor item in the menu bar's Help submenu as well as the Help menu and the About pane. If you'd rather not be asked, turn off "Show sponsor links" in Settings, About. WolfWave is free and complete either way.

Changed

  • Buttons that do slow work now show they're working. Approve, Skip, Hold, Fetch link, Check Again, Add to blocklist, and the developer service controls swap their label for a spinner while the work runs, ignore a second click, and flash a checkmark when they're done. Before, they looked untouched the whole time, so the only way to tell WolfWave had heard you was to click again and hope you hadn't just done it twice.
  • Stream Deck has its own settings page. It used to share Stream Widgets, with the control token sitting one divider below the overlay token in the same card. Those two do very different things: the overlay token is read-only and reachable from other machines on your network, while the control token runs commands and is only ever accepted from this Mac. Putting them side by side made them look interchangeable. Stream Deck is now its own page under On Stream, holding the control token, the setup steps, and a new Allow Stream Deck commands switch. Turn that off and WolfWave refuses every key press while your overlay keeps running, which is what you want if you like the now-playing box on stream but would rather nothing could drive playback. It's on by default, so nothing changes unless you change it.
  • Stream Widgets reads in the order you actually set it up. Turn the connection on, drop in the ready-made browser source, then style it. The port, the overlay token, and the raw WebSocket addresses moved to the bottom under "Build your own overlay", where they belong, because they're only needed if you're writing your own overlay. Leading with them made a two-click setup look like a programming job. The page also now tells you where the server can be reached (this Mac, or this Mac and your local network) instead of leaving you to guess, and the note explaining why the port is locked sits above the locked field rather than below it, where you'd only find it after wondering what was broken.
  • Stream Deck: three keys removed, and Clear Queue now has to be held. Discord Presence, Music Sync, Cycle Theme, and Status are gone. The first three flipped a setting you set once and then never touch, and a key on a deck is worth more than that; change them in Settings instead. Clear Queue still exists but only fires if you hold it for about a second, because it deletes every request with no undo. Update the plugin and WolfWave together — a version mismatch shows Update on every key. See Stream Deck.
  • Stream Deck keys use the whole key. A key that's on is now a solid blue key rather than a small blue glyph on black, so playing, queue held, and every toggle read from across the room. Clear Queue and Block Song are red because they throw something away, and Approve Next turns amber when requests are waiting. The queue keys print their count as a big number instead of squeezing it into the key label, and the Status key shows four dots, one per link, each in its own service colour, instead of a three-line list that never fit. Those keys also stop overwriting the key title, so a label you set on them now stays. See Stream Deck.
  • Export Logs now exports everything, not just the newest file. Settings → Advanced → Export Logs writes a single file containing your app version, macOS version, and install method, the details of the last crash if there was one, and every rotated log file rather than only the current one. Before, if the log had just rolled over you'd send us a nearly empty file while the lines about your problem sat in a backup we never saw. The file is named with the date and time so a second export doesn't overwrite the first, and Copy to Clipboard now includes the same version header.
  • The crash notice says what actually crashed. After an unexpected quit, the notice in Settings → Advanced now names the fault and when it happened instead of just saying something went wrong, and those details are written into your log so they're in the file you send us. "Report a Bug" also fills in the crash, your install method, and log size for you.
  • Logs you can actually read. Every line now carries a full date and timezone instead of just a clock time, so a log covering more than one day can be put in order. Each launch stamps the app version, build, and macOS version at the top, so a log you send us can be matched to the exact build that wrote it. Numbers that describe what happened (file sizes, timings, ports) are no longer scrubbed out as if they were account IDs, while tokens and account IDs are still removed. Export your logs from Settings → Advanced.

Fixed

  • The Stream Deck page's cards line up now. The "Setting it up" card was drawn narrower than the two above it, because it holds only text and a button and so shrank to fit them instead of filling the page like its neighbours. See Settings, Stream Deck.
  • The Stream Deck setup steps described an install you can't do. Step one told you to get the plugin from the Elgato Marketplace. It isn't there yet, you build it from the repo, which is what the setup guide has said all along. Step four also told you to leave the host alone; there is no host to change, so it now mentions only the port. See Stream Deck.
  • Hiding the overlay no longer blanks your Stream Deck. With the overlay cards turned off, WolfWave stopped telling the Stream Deck what was playing, so the Play/Pause key showed the track that happened to be on when you hid them, or nothing at all. Hiding the cards now only hides the cards. See Stream Deck.
  • Errors now tell you what broke and what to do about it. Failures used to say things like "Couldn't check channel" with no explanation, and the real reason was only visible if you hovered for a tooltip. Messages now name the cause and come with a button that performs the fix.
  • Twitch errors were invisible. Around forty failure messages, including every failed channel join and every Keychain problem, were written to a status line that no screen ever displayed. They now appear in the Twitch pane.
  • A missing permission no longer looks like an expired sign-in. If your Twitch token was missing an optional permission, WolfWave said your session had expired and told you to reconnect, then quietly stopped re-checking the token. It now names the missing permission, and everything else keeps working.
  • "Your sign-in is fine" when it is. If Twitch is down, rate limiting us, or you're offline, WolfWave used to blame your sign-in and send you to reconnect, which never helped. Those now say what actually happened.
  • Erase & Reset no longer stops silently. If macOS refused to remove your saved sign-ins, the reset stopped partway and said nothing, so it looked like everything had been erased when nothing had. It now tells you plainly that nothing was erased.
  • Discord says what went wrong. "Discord not running" was shown for every connection problem, including ones that happen with Discord open in front of you. WolfWave now distinguishes Discord being closed, Discord refusing the connection, and macOS blocking it. See Settings, Discord.

v2.1.0. August 14, 2026

Security

  • Safer local widget credentials. Token-bearing HTML is served only to a true loopback peer using a literal local Host and is marked no-store; token changes are saved to Keychain before connected clients rotate.

Added

  • Custom bot commands. Make your own chat commands with a fixed reply, in Settings → Twitch → Custom Commands. Use variables like $user, $touser, $args, $1$9, $song, and $lastsong and they fill in live. Each command gets its own aliases and Everyone / Per-person cooldowns, same as the built-ins. See Bot Commands.
  • Pick who can run a command. Set any custom command to Everyone, Subscribers, VIPs, Moderators, or Broadcaster only. Custom commands are included in your settings backup.
  • Approve songs before they play. New opt-in "Require My Approval" toggle in Settings → Song Requests → Access. When it's on, every request (chat, channel points, bits) waits in the Queue tab until you approve or decline it. Off by default, so requests still auto-queue unless you turn it on.
  • Fair-share song ordering. The queue interleaves requests round-robin, so everyone's first request plays before anyone's second. A regular who re-types fast no longer plays back-to-back while newcomers wait. On by default; flip "Fair-Share Ordering" off in Settings → Song Requests → Queue for classic first-in, first-out.
  • Sub / VIP request priority. Reward your subs, VIPs, and mods: pick a perk in Settings → Song Requests → Queue. "Skip cooldown" lets them request without the wait; "Jump the queue" also moves their song ahead of regular requests within the same fair-share round (a nudge up, not a queue takeover). Off by default.
  • Two new overlay layouts: Vinyl and Classic. Vinyl is a spinning record with your album art as the label and a circular progress ring. Classic sets the album tile beside a card with the title, artist, and a progress bar. Pick either in Settings → Stream Widgets. See Themes & layouts.
  • Queue ticker overlay. Add ?queueTicker=1 to your widget URL for a panel showing the next 3 song requests, title and requester, so viewers see their spot in line. Off by default, matches your current widget theme. See Queue ticker.

Changed

  • A custom WolfWave mark. The familiar wolf now carries a padded side equalizer, used consistently across the app icon, menu bar, onboarding, OBS fallback, Discord art, DMG installer, docs, and repository branding.
  • Settings toggles all look the same now. Launch at Login, "Check automatically" (Software Update), and "Collect on-device diagnostics" (Advanced) now use the standard row: name and short description on the left, switch on the right. Same layout as every other toggle in Settings.
  • Lighter on your Mac. WolfWave does a lot less work between songs now. Chat lines that aren't commands bail out immediately, the request poller sleeps when nothing is playing and the queue is empty, and Discord and your overlay only get an update when something actually changed instead of every few seconds. The History pane stopped re-sorting your whole play log twice on every redraw. Same features, less CPU and battery.
  • Lighter OBS overlay. Progress painting is batched at 10 Hz, and animation work stops while the page is hidden or unloaded and when reduced motion is enabled.
  • Less background work. WolfWave pauses unused overlay, Twitch, song-request, and Discord tasks instead of polling or retrying while those features are idle, disconnected, held, hidden, or disabled.
  • What's New only shows up for feature releases. The window used to reappear after every version bump, including bug-fix ones, showing a stale feature list. It now opens once per release that actually ships features.
  • Dev builds have their own app icon. If you build WolfWave from source, the Debug app now carries an orange icon instead of the blue one, so WolfWave Dev and the release app are easy to tell apart in the Dock and the app switcher. Nothing changes for anyone installing from a DMG or Homebrew.

Fixed

  • Settings no longer crashes on an out-of-range saved value. If a setting on disk held a value the control couldn't show, opening that pane could take the whole Settings window down instead of just looking wrong. The Song Requests pane did exactly that with a bad vote-skip window. Saved values are now checked as they're read, and anything out of range or corrupted shows as that setting's default until you change it.
  • Song request setup now checks the playlist Music can actually see. WolfWave makes the WolfWave Requests playlist in your Apple Music library, but songs play out of the Music app on this Mac. Setup only ever checked the first half, so it could tell you everything was ready while requests had nothing to play from. It now asks Music directly, and says so plainly if Music can't see the playlist, pointing you at Sync Library in Music, Settings, General. It's a heads-up, not a lockout: song requests stay on, and there's a Check Again button for once you've flipped it. See Features.
  • Settings backups now restore live behavior, not just switches. Appearance, update settings, and Launch at Login take effect during import; pending login-item approval points to System Settings, invalid or unrepresentable values are ignored, Song Requests stay off until setup is complete, and the preview count matches what will actually restore. Backups created by WolfWave 2.1.0 require 2.1.0 or newer to import; backups from earlier versions still import into 2.1.0. See Back Up & Restore.
  • Notifications no longer stack or arrive out of order. A new song or skip-vote banner clears both the queued and the already-shown banner it replaces, banners of the same kind are posted one at a time so a slow older one can't appear after a newer one, and a slow old artwork lookup can no longer overwrite the latest notification.
  • Cleared artwork stays cleared. An artwork lookup already in flight can no longer refill the cache after you clear it, and artist/title combinations containing punctuation no longer share cache entries.
  • Pasted music links resolve reliably. Song requests now find the first supported link in a message, ignore surrounding punctuation, and reject insecure or lookalike Apple Music URLs.
  • Twitch sign-in survives service hiccups. Device-code login now uses the original expiry deadline, follows the required polling slowdown and timeout backoff, and keeps trying through rate limits plus temporary network or server failures instead of failing early.
  • Twitch no longer mistakes an outage for an expired sign-in. Only Twitch's explicit token rejection or confirmed missing permissions prompts a reconnect. Network errors, rate limits, and malformed refresh responses retry within a bounded window and leave saved credentials intact when Twitch remains unavailable.
  • Disconnecting Twitch no longer gets stuck after a refund write fails. If WolfWave couldn't save a channel-point refund to disk and the account was then switched or removed, it kept retrying against credentials that no longer existed and held Disconnect open. It now stops once nothing can act on that redemption. The points are still refunded: the next sign-in for that reward reads your unfulfilled redemptions straight from Twitch and settles them before the reward goes live again.
  • Twitch account changes stay internally consistent. Access tokens, refresh tokens, account identity, and the configured channel now change together in one Keychain write. Existing saved fields migrate without deleting the originals until the new record is safe. Imported and edited channel names are checked with Twitch before becoming canonical; invalid, missing, stale, or temporarily unverifiable names stay pending.
  • Settings backups preserve authored rules. Custom bot commands and the Song Request blocklist now round-trip with other portable preferences, and Reset Settings clears the centralized blocklist data.
  • A backup that can't be applied safely is refused outright. Custom commands with a blank, duplicated, or clashing trigger, or with cooldowns the sliders can't represent, stop the import instead of half-landing. A file saved in an older, unsupported format now says so plainly rather than failing with a generic error. Blocked titles and artists get trimmed and checked on the way in, and keep the date you added them. See Back Up & Restore.
  • Channel names are tidied on the way in and out. An imported or exported Twitch channel name is cleaned up the same way the connect screen does it, so a stray space or a pasted URL fragment can't leave you with a channel that never connects.
  • Slow Twitch commands no longer hold up chat. Song requests and custom commands run as tracked connection work, so keepalives and later messages continue; disconnecting or switching channels cancels old replies before they can land in the wrong chat.
  • Paid Twitch actions recover after quits and outages. Channel-point intake and fulfill/refund outcomes are durable until Twitch acknowledges them. Qualifying Bits cheers persist the complete boost/request action before queue work and replay after relaunch. If local recovery storage fails, WolfWave pauses the managed reward, shows a warning, and still gives an already-paid Bits cheer one process-owned attempt.
  • Discord presence replies no longer cross wires or hang indefinitely. Each local IPC command consumes the response with its own nonce, the handshake accepts only Discord's READY dispatch, and stalled local connects or replies now stop at one bounded deadline.
  • Duplicate Twitch commands stay single-shot. Command message IDs keep their complete replay window even in busy chat, including while commands are disabled, so delayed EventSub redelivery cannot rerun queue or playback actions.
  • Local WebSocket peers can no longer hold unlimited resources. WolfWave caps pre-handshake peers, expires stalled handshakes, limits inbound messages to 16 KiB, and cleans every old listener generation before retrying.
  • Stream Deck reconnects no longer crash while a socket is still opening. Retiring a connecting socket now keeps the required error sink until close.
  • Stream Deck control is local-only and no longer shares your overlay secret. WolfWave now keeps separate read-only overlay and control tokens. Stream Deck protocol v2 always connects to this Mac, so LAN widgets can still receive track data but can never run playback or queue commands. After updating, copy the new Stream Deck Control Token into the plugin once.
  • Twitch custom commands stay locked while disconnected. The settings card now explains that Twitch must be connected and closes an open editor if the connection becomes unavailable.
  • The custom-command editor catches clashing aliases. It used to flag only a duplicated trigger, so an alias that shadowed another command slipped through and one of the two commands never fired. Commands saved before this check are cleaned up the next time WolfWave loads them. See Bot Commands.
  • Twitch stays connected when its server moves you. Twitch periodically hands your connection to a different server. WolfWave could flash "disconnected" and tear down the new, healthy connection while doing that. It rides through cleanly now.
  • Low Power Mode no longer stalls launch. Startup reads the posted power-state snapshot without recursively initializing the monitor.
  • Discord presence settings apply immediately. Idle presence and "hide while paused" changes refresh at once, preserve the paused track for restoration, and stay hidden when Discord is enabled mid-pause.
  • Apple Music stays closed when you close it. WolfWave no longer reopens Music.app after you quit it, and no longer opens it on its own while running.
  • WolfWave tells you when Music isn't open. The now-playing card used to say "Nothing playing right now" whether Music was closed or just idle. It now says "Apple Music isn't open" with an Open Music button when the app is shut.
  • Settings sidebar no longer opens too narrow. The sidebar could come back squeezed from a previous session and cut off section names like "Stream Widgets". It now uses a fixed width, matching System Settings, and a stale saved width can no longer be restored over it.
  • Version history opens instead of downloading. Clicking "version history" in the update window saved an HTML file to your Downloads instead of showing the changelog. The notes are embedded in the update feed now, so they render in place.
  • Overlays keep working after you change the token or port. Regenerating your access token or changing the port could leave every new OBS source getting rejected until you restarted WolfWave.
  • No more crashes from bad saved data. A corrupted settings backup could crash the app the next time chat ran !voteskip, and one bad line in your listening history could crash the History pane and !stats every time they drew. Both are handled now, and a full disk no longer takes the app down while it's writing a log.
  • No more long freezes from song requests. Every command WolfWave sends Apple Music now gives up after a couple of seconds, so a slow or busy Music.app can't lock the app up while the request queue auto-advances or when you hit play, pause, or skip.
  • Stream Deck overlay key stays connected. The action hides or shows overlay cards without dropping the authenticated control socket, so the next key press and its acknowledgement still work.
  • Album art stays current. A slow old lookup can no longer replace the current track's art, temporary download failures remain retryable, and the tray's decoded-art cache is bounded.
  • Advanced settings opens instantly. Log size and line count load in the background now, so a large log file no longer stalls the pane when it appears.
  • Accurate lifetime stats after a hard quit. Listening History no longer double-counts old plays if WolfWave is force-quit or crashes while it's trimming its history.
  • Song requests survive playback races. A failed or cancelled Apple Music start no longer loses the next request, and overlapping clears or queue edits while Music is responding can no longer revive an old item or overwrite the new order. Playlist setup coalesces simultaneous creation and stale-playlist recovery, follows every discovery page safely, and never adopts a same-name playlist that the user made.
  • Vote-skip stays with the poll and song that started it. Delayed or unrelated Twitch poll results can no longer skip a replacement track or clear the real active poll. If Twitch may have created a poll but did not return a trustworthy answer, WolfWave waits for timeout instead of opening a simultaneous chat tally or second poll.
  • History clears and rankings stay exact. Clearing history during its launch-time disk read can no longer restore old plays, finite retention no longer resurrects undated lifetime data, and tied stats and Monthly Wrap results are deterministic.
  • Clean Twitch sign-in cancel. Cancelling the Twitch connect flow no longer flashes a false "OAuth setup failed" error.
  • Steadier under the hood. Hardened Discord Rich Presence against a rare bad-playback-position crash, stopped a Twitch rate-limit wait from spinning the CPU when a connection drops, and made LAN IP detection skip a bad network interface instead of showing a blank overlay address.
  • "Open in Music" works in Song Requests setup. The button in step 4 (Share your playlist) did nothing. It now brings Music forward and selects your WolfWave Requests playlist.
  • Nightly testers can get back to Stable. Switching the Update Channel back to Stable now works on its own: the next stable release offers itself like any other update, instead of leaving you stuck until you manually reinstalled the DMG. One exception, one time: if you installed the August 5, 2026 nightly, grab any newer DMG once — that specific build can't update itself. See Nightly Builds.
  • Song requests and playback controls reach Apple Music again. Nightly builds from August 4, 2026 onward couldn't send Music.app a single command. The request queue never advanced to the next song, and !skip, !voteskip, the tray play/pause, and "Open in Music" all did nothing. Stable 2.0.1 was never affected. Music still stays closed when you close it.
  • The now-playing card stopped going blank while music played. If you played an album or a song straight from your library rather than from a playlist, WolfWave threw away a perfectly good track reading and showed nothing. The card, Discord Rich Presence, and your overlay all went empty until you happened to play from a playlist. Also affected nightly builds only; stable 2.0.1 was fine.

Developer

  • Docs restructure: merged the Features/Usage and Getting Started/Development pages, added Settings and Stream Deck Control API pages, trimmed the comparison page, and refreshed the legal pages.
  • Debug builds can treat every chatter, or selected Twitch logins, as normal viewers for permission and cooldown testing. Release builds omit the simulator.
  • Stream Deck control API. Groundwork for the upcoming Stream Deck plugin. The overlay WebSocket is now bidirectional: it parses a protocol-versioned inbound command envelope, routes 11 actions to existing services, and replies with an ack; new queue_state / health broadcasts drive counter/health keys. Still gated by the per-install wolfwave.token.<hex> handshake. The plugin itself ships separately.
  • Crash/lockup/data-race review pass. AppleMusicController runs NSAppleScript on a dedicated AppleScriptExecutor thread (own run loop) instead of the main thread; playbackSnapshot() and the playback commands are now async so the main-actor poll suspends rather than blocking. RateLimiter.awaitCapacity unwinds on cancellation, LifetimeTally gained a lastFoldedTimestamp high-water mark for idempotent crash-recovery reloads, and ListeningHistoryService coalesces concurrent loads.
  • queue_state gained a held field. Carries real request-queue hold state (SongRequestService.isHoldEnabled), and SongRequestHoldChanged now triggers a Stream Deck rebroadcast alongside SongRequestQueueChanged. Without it a hold key had to track its own optimistic toggle, which inverted the moment hold was changed from the tray, chat (!hold), or Settings. Additive on an outbound broadcast, so no protocolVersion bump — the version gates the inbound command envelope only.
  • Stream Deck plugin, Phase B (WW-45). New apps/streamdeck/ bun workspace consuming the Phase A control API. src/wolfwave/protocol.ts is the TypeScript mirror of StreamDeckCommand.swift (action tokens are the Swift enum's raw values; a test pins the full v1 set so the two can't drift), state.ts is a pure reducer that returns the same reference on an unchanged fold so per-second progress frames don't repaint keys, and client.ts holds one shared socket for all keys with capped reconnect backoff and FIFO ack correlation. Eleven key classes over a base that paints the disconnected / unauthorized / outdated states centrally, plus a Property Inspector that talks Elgato's protocol directly rather than loading sdpi-components from a CDN. Not shippable yet: no action icons, unverified on real hardware, and not packaged or submitted to the Marketplace. See Stream Deck and Architecture.
  • Steady-state performance pass. Ten hot-path fixes, all behaviour-preserving, each removing work that repeated every ~5s tick or per chat line. BotCommandDispatcher early-outs on a missing ! prefix before the command-table snapshot copy and per-command alias UserDefaults reads; SongRequestService.pollTick early-outs when nothing is playing and the queue is empty, skipping a 2s AppleScript round trip; AppleMusicController uses targeted runningApplications(withBundleIdentifier:) and caches compiled NSAppleScript by source; DiscordRPCService.updatePresence skips unchanged SET_ACTIVITY frames and WebSocketServerService.updateNowPlaying skips unchanged rebroadcasts; widget artwork only refetches on a genuine track change; MusicMonitorSettingsView stops restarting AsyncImage every tick; HistoryStatsSettingsView dropped a twice-per-body-pass O(n log n) sort over up to ~10k records; SongBlocklist.isBlocked lowercases once instead of per entry.
  • Concurrency correctness pass. Ten adversarially-verified race and leak fixes. TwitchChatService posts NotificationCenter from @MainActor (panes observe via .onReceive with no main hop) and bails out of handleReceiveError on Task.isCancelled so an orphaned receive loop can't undo a session migration; DiscordRPCService.connectIfNeeded re-validates generation/enabled/fd after its two-await handshake; WebSocketServerService detaches stateUpdateHandler before cancel and cancels failed connections; MonthlyWrapView.loadTint guards cancellation. Leaks closed in TwitchViewModel.deinit, TwitchChatService.deinit, and NotificationService. MusicPermissionCache traded nonisolated(unsafe) statics for @MainActor isolation.
  • Crash-guard pass. Four process-abort vectors reachable from external or user-writable data, none caught by the force-op lint gate. Int(Double) traps clamped in MetricsService.recordTwitchRateLimit, SkipVoteManager.sessionCooldown, and HistoryFormat.listeningTime/clock (plus sanitizing duration/playedSeconds at the PlayRecord decode boundary). Logger swapped six legacy raising synchronizeFile()/closeFile() sites for try? synchronize()/close(), which were aborting mid-error-handling on fsync ENOSPC/EIO.
  • OBS resource pass. The browser batches progress paint at 10 Hz and suspends when hidden/unloaded/reduced-motion; native progress work exists only while enabled, visible, playing, and serving clients, and JSON fan-out encodes once.
  • Idle-service pass. Twitch owns one keepalive/session with bounded classified retries and tears network work down on leave; Song Requests stops auto-advance polling while idle/held/disabled; Discord polls only while enabled and disconnected; decoded tray art is single-flight and bounded to 64 items / 32 MiB; startup duplicate warming was removed and reduced-power policy applies immediately.
  • DRY pass. 20 mechanical extractions with no behaviour change: shared HTTP-response / widget-config / now-playing / JSON-guard helpers, an EventSub body + form-POST + custom-rewards URL builder, shared day-bucketing and history-store filesystem helpers, a shared song-request dedup predicate and capacity insert, BotCommand.commandToken(in:) shared across the command match loops, SectionHeaderWithStatus reused at 10 hand-rolled header sites, and centralized setting + widget-appearance defaults.
  • Source layout split. No behaviour change: AppConstants into per-namespace extension files, DiscordRPCService into a pure DiscordPresenceBuilder plus an IPC seam, and the TwitchChatService actor across same-actor seam files (+Auth, +Connection, +EventSub, +Redemptions). See Architecture.
  • Dead code and stale docs removed. ~89 unused lines from ViewModifiers, plus doc-comment corrections across the playback monitor, SettingsView/SettingsNavRail, TwitchViewModel, AppConstants, SparkleUpdaterService, WidgetHTTPService, and the Discord / Twitch / WebSocket settings views, each verified against the actual code.
  • Swift file headers normalized. Every file now carries the header Xcode generates from IDETemplateMacros.plist, with each Created by date reset to the file's real git creation date. New scripts/check-headers.mjs (make lint-headers, plus a blocking CI job) keeps them from drifting.
  • CI hardening. A SwiftLint baseline gate blocks new violations without requiring the existing backlog to be cleared, and the full test suite runs unskipped on macos-26 (the weekly canary is retired). Sparkle appcast generation passes --embed-release-notes and fails the build if the notes aren't inline. Nightly releases are titled by their build commit's short SHA while keeping the fixed rolling nightly tag the appcast depends on.
  • Build/version standard across both feeds. New scripts/version.sh is the single source of truth for marketing version + build number; build_release.yml and nightly.yml both call it and inject MARKETING_VERSION / CURRENT_PROJECT_VERSION at build time, so the pbxproj build number is a dev-only placeholder and is no longer hand-bumped. The build number is git rev-list --count HEAD (floored) and shared by both channels — the retired nightly scheme used a UTC minute stamp (~2×10¹¹), about 100× over Android's versionCode cap and far enough above stable that no release could overtake it. Both workflows now require fetch-depth: 0; the resolver hard-fails on a shallow clone. Portable cross-platform rules in docs/build-versioning-standard.md. version.sh also warns when the committed MARKETING_VERSION still equals the newest v* tag, since nightlies advertise the next release version and that means the post-release bump-ahead was skipped.
  • AGENTS.md added (Codex guidance mirroring CLAUDE.md), and FEATURE_IDEAS.md retired in favour of Jira project WW.
  • Docs site and landing page pass. WCAG AA/AAA contrast in both themes (new --brand-fill token for white-on-blue fills), mobile hardening (40px+ tap targets, single-column stat grid under 420px, opaque mobile TOC backing, the sticky first table column the CSS comment had only promised), de-AI'd copy, the redundant "Open & trusted" proof band dropped with the section spine renumbered to a contiguous 01–08, per-page eyebrows moved from in-body spans to the route, corrected download / on-disk size claims measured against the shipped v2.0.1 artifacts, and Instrument Sans replacing Unbounded.
  • Settings-import hardening follow-up. SettingsBackupCoder.isValueAllowed became normalizedValue, turning the single validator into a canonicalizer shared by export, preview, and apply; channel names route through TwitchChatService.normalizedChannelName at all three, and a below-minimum schemaVersion raises BackupError.unsupportedOlderSchema. New CustomCommand.normalizedForImport / normalizedForExistingStorage reject or repair persisted commands (empty or overlapping triggers, duplicate UUIDs, out-of-range cooldowns), and BlocklistItem validates decoded entries while retaining persisted id and addedAt. NotificationService chains submission per PostGenerationDomain, closing a main-actor reentrancy window at center.add. Four keys missing from the AppConstants.UserDefaults classification were restored, so the blocklist genuinely round-trips and the key-coverage test asserts them.

v2.0.1. July 11, 2026

Fixed

  • Update notes now load. Sparkle's "What's New" window no longer 404s when you check for updates. (It broke in 2.0.0.)
  • Fewer crashes. Fixed a crash for people who opted into diagnostics, a crash from an out-of-range widget port, and hard failures when a log write hit a full disk.
  • Music.app can't freeze WolfWave. Every command to Apple Music now times out instead of hanging the app if Music gets stuck.
  • Snappier menu bar. Play/pause state reads from cache instead of pinging Music.app while the menu is open.
  • No double actions. Twitch events that arrive twice (chat commands, channel-point redemptions) only run once now.
  • Overlays keep updating. Closing the Settings window no longer stops now-playing updates from reaching your overlay.
  • Vote-skip won't get stuck. A Twitch poll that never closes now falls back cleanly, and chat votes reset between songs.
  • Accurate history. Listening History no longer double-counts a play, and tells apart songs that share a title.
  • !playlist survives hiccups. A brief network blip while checking your playlist link no longer switches the command off.

v2.0.0. July 1, 2026

Security

  • Stream Widgets now need an access token. Every WebSocket client must authenticate with a per-install token kept in the Keychain. Localhost HTML receives it from WolfWave; LAN links carry it as connection bootstrap. Existing OBS sources keep working. Reveal, regenerate, or replace the token in Settings → Stream Widgets.
  • Streamer Mode. One tray toggle hides anything you wouldn't want on camera: channel name, overlay and widget URLs, and the access token. Copy and Open buttons switch off too. On-screen only. Your overlay, Discord, and chat output are untouched. See Streamer Mode.

Added

Song requests

  • Song Requests. Viewers request tracks with !sr <song>. Plays through Apple Music without stealing focus from your stream. See Bot Commands.
  • Guided setup. A quick walk-through (Twitch, Apple Music access, your requests playlist) runs before song requests can be switched on, so nothing goes live half-configured. If your requests playlist is ever deleted or un-shared, WolfWave catches it and shows a "set up again" banner.
  • Queue commands. !queue and !myqueue show the queue. !skip/!next, !clearqueue, and !hold/!resume are for mods and the broadcaster.
  • Chat Vote-Skip. Chat votes out a song with !vs. Counts unique voters, or opens a native Twitch poll (Affiliate/Partner). Set it up in Settings → Song Requests.
  • Song Request Queue view. A now-playing card, who asked for each track, and Skip / Hold / Clear.
  • Hold the queue. New requests pile up without auto-playing, so you can line them up first. From the queue view or the menu bar.
  • Requests wait while Apple Music is closed. Saved, then played when Music reopens.
  • Fallback playlist. Plays an Apple Music playlist whenever the queue runs dry.
  • Limits and controls. Per-user and global limits, subscriber-only mode, on/off per command, and custom aliases.
  • Recreate Reward button. Rebuilds the "Request a Song" Channel-Point reward if you delete it by hand.

Your music, tracked

  • Listening History. An opt-in, on-device log of what you play. Stays on your Mac, never uploaded. Off by default. Skips don't count. See Listening History.
  • Stats and Charts. Top artists, listening time, a 7-day trend, and a by-hour breakdown. Needs Listening History on.
  • Monthly Wrap. A personal "Wrapped" for any month. Save it as a PNG or share it.
  • History retention picker. Keep history forever, or for 7 / 30 / 90 / 180 / 365 days.
  • !stats command. Chat asks for today's top track. Only replies while you're live.

On stream

  • !wolfwave command. A one-tap chat shoutout for the app. Four reply styles. Off by default.
  • Widget themes and layouts. Five selectable themes (Default, Dark, Light, Glass, Neon), plus a hidden internal WolfWave theme, and five layouts (Horizontal, Vertical, Compact, Vinyl, Classic). See Stream Widget.
  • Live widget preview. See your overlay update as you tweak the theme, layout, font, and colors.
  • Smoother OBS widget. Songs slide in, fade out calmly, and crossfade on fast skips. No strobing.
  • Discord idle and pause controls. Keep an idle marker when nothing plays, or clear your profile while paused. Both off by default.
  • Discord playlist in your presence. Shows the current Apple Music playlist next to the track.
  • Song-change notifications. An optional macOS banner on every track change. Off by default.
  • Skip-vote notifications. Optional banners when a vote starts and when it passes. Off by default.

The app

  • Light, Dark, or System appearance. Pick one in Settings → General. The menu bar follows too. See Appearance.
  • Back up and restore your settings. Export to a JSON file, restore on another Mac. Credentials and private account IDs stay in the Keychain; the public Twitch channel name travels with your preferences. On import, you choose whether to reconnect Twitch. See Back Up & Restore.
  • macOS 26 Liquid Glass redesign. Settings, menu bar, and a rebuilt onboarding wizard for Tahoe.
  • Nightly update channel. Opt into dev builds off main from Settings → Software Update. Clearly warned, easy to leave, and Stable stays the default. See Nightly Builds.
  • Diagnostics and bug reporting. Export/Clear Logs and a one-click Report a Bug, with sensitive details redacted.
  • On-device diagnostics. An opt-in MetricKit report with a share card. Stays on your Mac. Off by default.
  • Branded About window. A native WolfWave About replaces the macOS default.
  • WolfMark branding. A new album-art placeholder and brand polish across the app and overlay.
  • Faster overlay URLs. WolfWave caches your local IP, so settings no longer pause looking it up.

Changed

  • Clearer settings headings. Every pane has one big title with section titles a step below, so it reads top to bottom at a glance. VoiceOver can jump between them by heading.
  • Redesigned Settings sidebar. A branded header, grouped rows with round icon chips, and an accent-colored selection pill. Sections are grouped by what they're for.
  • Calmer destructive buttons. Reset, Clear Logs, and Clear Artwork Cache drop the loud red bar for a neutral button with a red label. The Danger Zone card gets a red heading instead of a red wash.
  • Type RESET to wipe everything. The Reset confirmation spells out what it erases and makes you type RESET first, so a stray click can't reset the app.
  • Apple Music playback keeps your focus. Music never steals focus from OBS or other streaming tools.
  • MusicKit is only used for search, never for playback.
  • Steadier under stress. A safety net catches the rare hard crash. If WolfWave goes down, it shows a one-time "Recovered from a crash" notice in Advanced with a one-click bug report.
  • Permissions live in one onboarding step. Apple Music access, the notification prompt, and the per-alert toggles are now together.
  • Onboarding polish. Repeated success rows gone, tighter wording, and pinned sizes so nothing jumps around. VoiceOver announces each step by name.
  • Music permission row is actionable. Tap it to re-request access or jump into System Settings.
  • App Visibility is a single-column picker for the menu bar, the Dock, or both. General lays Startup and Display Mode side by side.
  • Tighter wording across onboarding, settings, and the menu bar.

Performance

  • Lower idle energy use. Background timers now let macOS batch their wakeups together, which saves power for an all-day menu bar app. Real-time track changes still arrive instantly.
  • No more jank when switching between settings sections.
  • Faster first paint and an instant Now-Playing Server row, thanks to off-main-thread font lookup and the cached local IP.

Developer

Developer-facing changes. Not visible to end users.

  • Unified design system. One design-system/tokens.json feeds a generator that emits four platform outputs (Swift, docs CSS, widget JS, marketing TS), with a component catalog and a Turbo tokens build prerequisite.
  • OBS widget build pipeline. The widget source moved to a new apps/widget/ Tailwind + TypeScript workspace; widget.html is now a generated, fully-inlined artifact rebuilt explicitly or by CI and committed for Xcode to bundle. See OBS Widget Architecture.
  • Sparkle hardening. Opted out of system-profile telemetry, and the release pipeline turns each version's CHANGELOG section into styled HTML embedded in the appcast (scripts/release-notes.mjs).
  • Crash safety net. An uncaught-exception and signal handler records a breadcrumb and chains the previous handler, plus a blocking force-unwrap CI lint gate on production source.
  • Concurrency and architecture cleanup. HelixClient, a typed Preferences accessor, typed NotificationCenter payloads, actor adoption for SongBlocklist and SkipVoteManager, and Combine fully removed from the app target.
  • DEBUG-only Debug settings tab with inspectors, service controls, log/event views, and What's New previews.
  • New unit tests and an expanded design-token roster across the suite.

Fixed

  • Twitch bot no longer goes silent after a relaunch. It reconnects to your saved channel on its own, so chat commands work without reopening Twitch settings. The reconnect is silent.
  • One recovery card for denied Music access. No more stacking the same "Music access denied" warning three times. A single card owns the explanation and the Open System Settings button. "Try again" shows a spinner instead of feeling like a dead button.
  • Now-playing survives Apple Music quirks. The player-state reader accepts every value Music returns, instead of blanking the now-playing card, Discord presence, and overlay while music is playing.
  • Apple Music control under the sandbox. The "WolfWave wants to control Music" prompt now appears and WolfWave can actually drive Music.
  • Two-PC stream widget works. Open your overlay from a second computer or your phone on the same network. The Network Address URL in Settings → Stream Widgets now works (gated by the access token).
  • Twitch settings card stops jumping between the signed-out, authorizing, and error states.
  • Onboarding is steadier. Steps are centered again, the header no longer drifts between steps, and the Preferences step stops overlapping the nav bar on short windows.
  • Recheck and the Sync toggle force a fresh now-playing read, so the UI catches up right after you grant permission.
  • Apple Music Recheck button visibly responds when tapped, instead of looking broken.
  • Stats card layout no longer jumps around and is gated on Apple Music access.
  • Export Logs no longer crashes on empty or very large logs.
  • About window Check for Updates now actually runs Sparkle, and Release Notes points at this changelog.
  • Intel Homebrew detection. Intel Macs installed via Homebrew now disable Sparkle and show the Homebrew update card.
  • Keychain self-heals a duplicate item that could appear after onboarding.
  • Settings sidebar toggle cleaned up: one toggle, no floating reveal control.
  • Cleaned up build warnings, layout glitches, duplicate log lines, and small icon and window-sizing issues.

Removed

  • WallpaperBloomBackground, in favor of native macOS chrome.
  • Repeated onboarding success rows.

v1.2.0. April 4, 2026

Added

  • Shared UI components. Reusable settings components for a more consistent look

Changed

  • Music architecture. Refactored to a pluggable source system, laying groundwork for future music sources beyond Apple Music
  • AppDelegate decomposed. Split into focused extensions for maintainability
  • Logger. Streamlined format with local time and emoji prefixes
  • Widget settings. Compact 2-per-row layout, auto-sizing dropdowns
  • Settings UI. Unified headers and test buttons across Discord & Twitch sections

Fixed

  • Settings window not appearing when opened from menu bar in menu-bar-only mode
  • 4 Xcode build warnings (actor isolation and unreachable code)
  • Widget favicon broken reference

Full changelog on GitHub

v1.1.0. March 31, 2026

Added

  • Discord buttons. Rich Presence now shows two clickable buttons: Open in Apple Music (direct track link) and song.link (opens on Spotify, YouTube Music, Tidal, and more)
  • Launch at Login. New toggle in Settings → App Visibility. Uses SMAppService, appears in System Settings → General → Login Items
  • Custom DMG background. Installer window has a polished dark background with WolfWave brand colors
  • Homebrew auto-update. GitHub Actions now automatically opens a pull request on the Homebrew tap on new releases

Fixed

  • iTunes Search API URL encoding. Track/artist names with &, +, or = no longer break artwork lookups
  • Launch at Login toggle now reverts if SMAppService registration fails

v1.0.2. March 31, 2026

Fixed

  • App icon missing in CI-built releases
  • Sparkle updater unable to detect new versions (build number now incremented per release)
  • Sparkle initialization race condition fixed

v1.0.1. March 30, 2026

Changed

  • Dropped Intel (x86_64) support. Apple Silicon only
  • Raised minimum macOS version to 26.0 (Tahoe)
  • Logger: replaced NSLock with serial DispatchQueue for thread-safe file I/O
  • TwitchChatService/DiscordRPCService: documented thread safety patterns
  • KeychainService: added error logging for all Keychain operations
  • AppConstants: cached GitHub repo resolution
  • Migrated TwitchViewModel and OnboardingViewModel to @Observable macro
  • WhatsNewView: dynamic version string, native button style, v1.0.1 feature highlights
  • Narrowed entitlements file exception path
  • Added Twitch user ID redaction to log output
  • NotificationCenter observers properly cleaned up on app termination
  • Windows (Settings, Onboarding, What's New) properly released on close
  • Deferred Sparkle/onboarding init past initial layout to fix layoutSubtreeIfNeeded warning
  • Removed duplicate "up to date" alert (Sparkle handles it natively)
  • Added VoiceOver accessibility labels across all settings and onboarding views

v1.0.0. March 30, 2026

Added

  • Native macOS menu bar app for Apple Music integration
  • Real-time now-playing detection via ScriptingBridge
  • Twitch chat bot with !song, !currentsong, !nowplaying, !lastsong, !last, !prevsong commands
  • Discord Rich Presence with dynamic album art
  • OBS stream widget via built-in WebSocket server
  • Automatic updates via Sparkle (DMG) or Homebrew
  • First-launch onboarding wizard
  • macOS Keychain credential storage
  • Bot command cooldowns and broadcaster bypass
  • Settings UI with NavigationSplitView sidebar
  • Diagnostic log export

For the full changelog, see CHANGELOG.md on GitHub.

On this page