Gitea has no /releases/latest/download/ redirect and release URLs always
include the tag, so updaters that poll a constant URL (Shelly) cannot use
them. After publishing a vX.Y.Z release, replace the asset on a rolling
pre-release tagged "latest":
.../releases/download/latest/WhatsThatMusic-latest-linux-x86_64.AppImage
Replacing the asset changes its ETag and Last-Modified, which Shelly
compares. The pre-release flag keeps Gitea's own "latest release" on the
newest vX.Y.Z. The logic lives in .gitea/scripts/update-latest.sh so it can
be run against the API outside CI. The publish job now checks out first,
since checkout cleans the workspace and would delete downloaded artifacts.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
- Read the track from the Media Session API, falling back to the legacy
ytmusic-player-bar scrape. Current YT Music layouts render no player bar,
so the API and tray stayed empty while music played. Artist is also clean
now (no "• views • likes" suffix).
- Terminate connected WebSocket clients before closing the server. ws's
close() waits for every client to disconnect, so quitting with an overlay
open hung forever, leaving the window playing and the port bound.
- Replace the tray icon with a valid PNG (the old one had a bad IDAT CRC and
decoded to an empty image) and test that its chunks are well formed.
- Create the tray before the window so early state updates are not dropped.
- Bump version to 1.0.1.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The publish job has never run successfully. When it failed on the v1.0.0
tag it reported "SyntaxError: Unexpected end of JSON input" from piping an
empty curl body into JSON.parse, which said nothing about the actual cause
(an unset token). An opaque failure there costs a full three-platform build.
Routes every call through a helper that captures the HTTP status and prints
the response body on 4xx/5xx. Also switches artifact iteration to while-read
so filenames with spaces cannot split, and dumps the artifact tree when
nothing matches.
Helper verified against the live API: existing tag resolves to its release
id, a missing tag reports HTTP 404 with the body and yields an empty id so
the create path runs.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The mac job targeted macos-arm64, but the registered Mac runner is
labelled macos-latest, so that job would have queued forever rather than
failing -- a job with no matching runner is silently pending.
Also switches the publish step to secrets.GITEA_TOKEN, which Gitea injects
automatically and scopes to the repo, instead of requiring a hand-created
RELEASE_TOKEN. RELEASE_TOKEN still overrides it if wider rights are needed.
Runners confirmed via /api/v1/admin/actions/runners: ubuntu-latest,
windows-latest (x2) and macos-latest are all online.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Splits the release workflow into linux, windows and macos jobs feeding a
single publish job. The publish step is separate so parallel builds cannot
race to create the release and fail on a duplicate tag.
Windows now builds natively, so signAndEditExecutable no longer has to be
disabled. That flag was suppressing rcedit, which is what embeds the icon
and version resources -- the v1.0.0 exe shipped with the stock Electron
icon as a result. Also adds an NSIS installer alongside the portable zip.
macOS gains a dmg target, which cannot be produced off macOS. Builds stay
unsigned pending a Developer ID, with CSC_IDENTITY_AUTO_DISCOVERY disabled
so electron-builder builds unsigned instead of failing on a real Mac.
Nothing cross-builds now; all three runners must be registered before
tagging, since a job with no matching runner queues silently.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Adds a mac zip target (x64 + arm64) so releases cover all three
platforms. Builds unsigned: electron-builder skips code signing when not
running on macOS, so these need the quarantine attribute cleared.
Also puts ${os} in artifactName. Without it the Windows and macOS zips
were both WhatsThatMusic-1.0.0-x64.zip, which collide as release assets
and give no clue which platform they are.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Builds the Linux AppImage on a v* tag, runs the test suite first, and
attaches the artifact to a Gitea release via the API (no third-party
action, so nothing needs to be fetched from github.com at runtime).
Guards that the tag matches package.json version: electron-builder names
the artifact from package.json, so tagging v1.0.1 without bumping the
manifest would otherwise publish an AppImage named 1.0.0.
Requires a RELEASE_TOKEN secret and a registered act_runner; neither is
set up yet, so this is inert until both exist.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The build config only had a Windows target. Adds a linux block producing
an AppImage, plus a 512x512 build/icon.png so the packaged app does not
ship with the stock Electron icon.
Verified by running the packaged binary: the embedded server comes up on
9095 and serves the overlay from process.resourcesPath, confirming the
extraResources mapping works in a packaged build.
StartupWMClass is deliberately not set via linux.desktop -- electron-builder
already emits the correct value, and the nested { entry: {...} } form leaks a
literal "entry=[object Object]" line into the .desktop file.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Removes extension/, desktop-client/backends/, desktop-client/config.js,
server/, root package.json/package-lock.json/node_modules/, and the
now-unused electron-store dependency from desktop-client/package.json.
The desktop-client is now a self-contained Electron app with no external
deps beyond express and ws.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Removes top-level let trayInstance, opts, currentTitle, currentArtist
and the top-level buildMenu function. All mutable state and buildMenu
now live inside the createTray closure. Public API and behaviour are
unchanged.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Bite-sized, TDD-where-applicable tasks covering branch setup, state
merge unit tests, server module, preload scraper, Electron main
rewrite, tray rewrite, manual smoke tests, Windows zip build, and
cleanup of the superseded extension/server/OS-detection code.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the OS-detection / browser-extension input architecture with
a single Electron app that embeds music.youtube.com and pushes
now-playing state to an in-process server on localhost:9095.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Express was matching static files before API routes, causing the
image-proxy endpoint to return 404 in browser.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Node.js server on port 9095 receives now-playing data via POST and broadcasts
to connected WebSocket clients. Includes a Firefox/Chrome extension that scrapes
YouTube Music's player bar every 3s, an OBS-ready transparent overlay with
animated equalizer bars, and an image proxy to bypass CDN restrictions in OBS.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>