diff --git a/CHANGELOG.md b/CHANGELOG.md index f5cfbd9..da48717 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,6 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -Offstream has not shipped a release yet; everything below is work towards the first one. Because Offstream succeeds **Spytify** (.NET Framework 4.6.1 / WinForms) rather than starting from nothing, the `Changed`, `Removed` and `Fixed` entries are written against that predecessor — they say how Offstream differs from the app it replaces. `docs/MODERNIZATION-PLAN.md` is the @@ -13,6 +12,12 @@ phase plan these entries follow. ## [Unreleased] +## [0.1.0] - 2026-08-15 + +The first release. Everything below is the whole of Offstream rather than a change to it: +phases 0–8 of `docs/MODERNIZATION-PLAN.md`, from the .NET 10 retarget through to a per-user +installer. + ### Added - **The licences are readable from inside the app, not just present as files in the folder.** @@ -28,6 +33,15 @@ phase plan these entries follow. Last.fm now says whose data is being written into the recordings — an attribution Spotify's Developer Terms require and that the app had never carried anywhere. It is empty when no provider is selected, because crediting a service the app is not calling would be a false statement. +- **The Settings page names which Spotify account is signed in**, as display name and account id + — not just that one exists. Recordings go untagged when the signed-in account is not the one + the music plays on, and nothing on screen says which it is, so the only way to find out was to + read the logs. Costs one scope, `user-read-private`. The id is there because a display name is + not an identifier: two accounts can share one, and telling exactly those apart is what this is + for. `user-read-email` would be the obvious way to do that and is deliberately not requested — + Spotify removed the `email` field in its late-2024 cull, so that permission now covers data the + endpoint no longer returns. + - **A release pipeline, with the git tag as the only place a version number lives.** Pushing `v1.2.3` builds, tests, publishes, signs and attaches a self-contained `win-x64` zip and its SHA-256 to a GitHub release; the changelog becomes the release notes. Nothing in the repository records a @@ -329,18 +343,6 @@ phase plan these entries follow. harness nothing built on, carried in the solution and the layout docs as though it were part of the app. `docs/decisions/0001-phase-0-retarget-spike.md` keeps the findings. -### Added - -- **The Settings page names which Spotify account is signed in**, as display name and account id - — not just that one exists. Recordings go untagged when the signed-in account is not the one - the music plays on, and nothing on screen said which it was, so the only way to find out was - to read the logs. Costs one scope, `user-read-private`, and existing installs show nothing - here until they sign in again, since a refresh token predating the scope cannot read the - profile. The id is there because a display name is not an identifier: two accounts can share - one, and telling exactly those apart is what this is for. `user-read-email` would be the - obvious way to do that and is deliberately not requested — Spotify removed the `email` field - in its late-2024 cull, so that permission now covers data the endpoint no longer returns. - ### Fixed - **Pressing record with Spotify paused, then pressing play, recorded nothing.** The level meter diff --git a/CLAUDE.md b/CLAUDE.md index 54d7736..4d5055d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -63,7 +63,7 @@ Set by the user on 2026-08-13 and binding like the constraints above. Most were - **Endpoints, parameters and field names come from the [OpenAPI schema](https://developer.spotify.com/reference/web-api/open-api-schema.yaml).** Never guessed. In practice `SpotifyAPI.Web` is the typed SDK and nothing here hand-rolls HTTP; check the schema before adding a call, not after. - **[Authorization Code with PKCE](https://developer.spotify.com/documentation/web-api/tutorials/code-pkce-flow) for user data.** Client Credentials only for public non-user data. **Implicit Grant is banned** — it is deprecated. A desktop app is a public client, so there is no Client Secret to protect and none may be introduced. - **Redirect URIs are HTTPS**, except `http://127.0.0.1` for local development. Never bare `localhost` (Spotify rejects it), never wildcards. `SpotifyAuthOptions.DefaultRedirectUri` is the one in use and a test pins its shape. -- **Request the minimum [scopes](https://developer.spotify.com/documentation/web-api/concepts/scopes) the shipped features need, never a scope for a feature that might arrive later.** Offstream makes exactly two calls: `/me/player/currently-playing`, which needs `user-read-currently-playing`, and `/albums/{id}`, which needs no user scope. That is the whole list, and `SpotifyAuthOptionsTests` fails the build if it grows. A scope requested ahead of its feature is a permission the user grants for nothing, on a consent screen where the spare lines look identical to the load-bearing one. +- **Request the minimum [scopes](https://developer.spotify.com/documentation/web-api/concepts/scopes) the shipped features need, never a scope for a feature that might arrive later.** Offstream makes exactly three calls: `/me/player/currently-playing`, which needs `user-read-currently-playing`; `/albums/{id}`, which needs no user scope; and `/me`, which puts the signed-in account's name on the Settings page and needs `user-read-private`. Two scopes, and `SpotifyAuthOptionsTests` fails the build if the list grows. `user-read-email` would identify an account unambiguously and is deliberately **not** requested: Spotify removed the `email` field in its late-2024 cull, so the permission now covers data the endpoint no longer returns. `SpotifyAuthOptions.DefaultScopes` carries the full reasoning and is the place to change it. A scope requested ahead of its feature is a permission the user grants for nothing, on a consent screen where the spare lines look identical to the load-bearing one. - **Tokens are stored protected, refreshed, and abandoned when dead.** DPAPI covers the refresh token; the access token never reaches disk. Spotify rotates the refresh token on every renewal, so the replacement must be persisted or a long-running install silently stops working. A **401** means the refresh token itself is gone — clear it and put the user back through sign-in rather than retrying it forever. Only 401: treating a rate limit or an outage that way would sign the user out over a transient fault. - **On 429, honour `Retry-After` exactly; back off exponentially only where there is no such instruction.** Guessing shorter is what gets an application throttled harder. Never retry in a tight loop. `SpotifyRetryHandler` is the implementation — note that `SpotifyClientConfig.CreateDefault()` attaches **no** retry handler, so a client built without one treats rate limiting as fatal. - **Log throttling at `Warning`.** The Record page's activity log shows Information and above, so anything quieter is invisible to everyone who has not gone looking. Transient 5xx stays at `Information` — it usually clears, and promoting it makes the Problems filter too noisy to read. diff --git a/README.md b/README.md index 8e767ce..7e5744b 100644 --- a/README.md +++ b/README.md @@ -34,10 +34,11 @@ drops into the list below as it is completed.* Three things worth knowing up front, so nothing is a surprise: -**There's no release yet.** Offstream is still in development, so there is nothing to download — you -build it from source, which is a few downloads and one build, with every step below. The installer -and the release pipeline are built and waiting on a version tag; check -[Releases](https://github.com/revtex/offstream/releases) before assuming this is still true. +**Download it from [Releases](https://github.com/revtex/offstream/releases).** The installer is +per-user and never asks for administrator rights; the zip is the same build if you would rather not +install anything. **Neither is code-signed**, so Windows SmartScreen warns the first time you run +it — choose **More info**, then **Run anyway**, after checking the download against the SHA-256 in +the release notes. Building from source is the other option, and every step is below. **It records your PC's sound, not Spotify specifically.** By default Offstream captures whatever your speakers are playing, so a Windows notification chime or a YouTube tab in the background lands @@ -46,10 +47,11 @@ in the recording too. Either keep the machine quiet while it records, or install settings, and record that instead — then only Spotify is captured. Offstream tells you on the Settings page whether VB-CABLE is installed. -**You need ffmpeg — for now.** It's the free tool Offstream uses to turn the captured audio into -MP3s. One command installs it; see [Getting Offstream running](#getting-offstream-running). -Releases bundle their own copy, so this is only true while building from source. If you have one -installed anyway, or point Offstream at a particular build on the Settings page, yours wins. +**You need ffmpeg only if you build from source.** It's the free tool Offstream uses to turn the +captured audio into MP3s, and releases bundle their own copy in an `ffmpeg` folder beside the +executable. One command installs it otherwise; see +[Getting Offstream running](#getting-offstream-running). If you have one installed anyway, or point +Offstream at a particular build on the Settings page, yours wins over the bundled one. ## Getting Offstream running