Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
64ecd93
feat(rust): manage Linux Spotify updates from the client (#3953)
afonsojramos Sep 17, 2026
b50800b
fix(rust): make Windows release test sockets blocking (#3954)
afonsojramos Sep 17, 2026
d1be9e9
feat(pkg): add update subcommand for modules
Sep 17, 2026
b682895
fix(rust): preserve native ARM64 and x64 Spotify on Windows (#3969)
afonsojramos Sep 29, 2026
f330d10
test: remove tests that do not exercise behaviour (#3970)
afonsojramos Sep 29, 2026
b56fd6b
chore: follow the spicetify/classmaps schema 2 layout (#3972)
afonsojramos Sep 29, 2026
7f7cbd8
refactor(scripts): port the classmap capture tooling to TypeScript (#…
afonsojramos Sep 29, 2026
acaf762
ci: test the classmap tooling on v3-beta pull requests (#3974)
afonsojramos Sep 29, 2026
c1e8b9e
feat(scripts): verify a key's css-map overlay over CDP (#3976)
afonsojramos Sep 29, 2026
cae28f6
chore(release): 3.0.0-beta.20 (#3975)
afonsojramos Sep 29, 2026
1f9be4a
fix(scripts): reach every classmap surface in the deep CDP run (#3977)
afonsojramos Sep 30, 2026
70a1b2d
feat(scripts): match CDP menu labels in any language and finish on sl…
afonsojramos Sep 30, 2026
38c2f5b
fix(rust): keep Spotify running when Apply lacks write access (#3971)
afonsojramos Sep 30, 2026
3b58550
fix(rust): restore Store and Settings on classmap fallback (#3968)
afonsojramos Sep 30, 2026
a3229a3
Merge remote-tracking branch 'origin/v3-beta' into pr-3955
afonsojramos Sep 30, 2026
65f68ca
fix(pkg): update only store-managed modules the user left enabled
afonsojramos Sep 30, 2026
5772bee
fix(pkg): give apply and pkg update their own refresh policies
afonsojramos Sep 30, 2026
f989020
fix(pkg): identify explicit installs by checksum
afonsojramos Sep 30, 2026
5d6bc2d
refactor(module): extract checksum normalization
afonsojramos Sep 30, 2026
49928f0
fix(pkg): keep unlisted URL installs during a bulk update
afonsojramos Sep 30, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ jobs:
run: node --test "src/jsHelper/**/*.test.mts"

- name: Run classmap tooling tests
run: python3 -m unittest scripts.test_classmap_capture
run: node --test "scripts/*.test.mts" "scripts/*.test.mjs"

- name: Format
run: |
Expand Down
51 changes: 51 additions & 0 deletions .github/workflows/classmap-tooling.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
name: Classmap tooling

on:
pull_request:
branches:
- "main"
- "v3-beta"
- "*/main/*/**"
paths:
- "scripts/classmap-*"
- "css-map.json"
- "package.json"
- "pnpm-lock.yaml"
- ".github/workflows/classmap-tooling.yml"
push:
branches:
- "main"
- "v3-beta"
- "*/main/*/**"
paths:
- "scripts/classmap-*"
- "css-map.json"
- "package.json"
- "pnpm-lock.yaml"
- ".github/workflows/classmap-tooling.yml"

permissions:
contents: read

jobs:
test:
name: Test classmap tooling
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Setup Node and pnpm
uses: jdx/mise-action@7e36c90d9ab29c415a2384db3006f3ec8a8cc654 # v4.2.4

- name: Install Node dependencies
run: pnpm install --frozen-lockfile

- name: Lint
run: pnpm exec oxlint scripts/classmap-capture.ts scripts/classmap-capture.test.mts scripts/classmap-cdp-verify.mjs scripts/classmap-cdp-verify.test.mjs

- name: Check formatting
run: pnpm exec oxfmt --check scripts/classmap-capture.ts scripts/classmap-capture.test.mts scripts/classmap-cdp-verify.mjs scripts/classmap-cdp-verify.test.mjs

- name: Test
run: node --test "scripts/classmap-*.test.mts" "scripts/classmap-*.test.mjs"
10 changes: 10 additions & 0 deletions .github/workflows/rust-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ jobs:
target: x86_64-pc-windows-msvc
asset_os: windows
asset_arch: x86_64
- os: windows-11-arm
target: aarch64-pc-windows-msvc
asset_os: windows
asset_arch: aarch64
steps:
# A dispatch-supplied tag reaches checkout's ref, so it is constrained to
# the shape this workflow publishes before anything acts on it.
Expand All @@ -57,6 +61,12 @@ jobs:
with:
ref: ${{ inputs.tag || github.ref }}

- name: Configure native Windows ARM64 toolchain
if: runner.os == 'Windows' && runner.arch == 'ARM64'
shell: pwsh
working-directory: .
run: ./scripts/setup-windows-arm64.ps1

- name: Setup Node and pnpm
uses: jdx/mise-action@7e36c90d9ab29c415a2384db3006f3ec8a8cc654 # v4.2.4

Expand Down
26 changes: 22 additions & 4 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ on:
- "src/jsHelper/**"
- "install.sh"
- "install.ps1"
- "scripts/test-install-completions.*"
- "scripts/test-install-*.ps1"
- "scripts/test-install-completions.sh"
- "scripts/setup-windows-arm64.ps1"
- ".github/workflows/rust-release.yml"
- "scripts/build-payload.mjs"
- "package.json"
- "pnpm-lock.yaml"
Expand All @@ -26,7 +29,10 @@ on:
- "src/jsHelper/**"
- "install.sh"
- "install.ps1"
- "scripts/test-install-completions.*"
- "scripts/test-install-*.ps1"
- "scripts/test-install-completions.sh"
- "scripts/setup-windows-arm64.ps1"
- ".github/workflows/rust-release.yml"
- "scripts/build-payload.mjs"
- "package.json"
- "pnpm-lock.yaml"
Expand All @@ -42,14 +48,20 @@ jobs:
strategy:
fail-fast: false
matrix:
os: [macos-latest, ubuntu-latest, windows-latest]
os: [macos-latest, ubuntu-latest, windows-latest, windows-11-arm]
defaults:
run:
working-directory: rust
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Configure native Windows ARM64 toolchain
if: runner.os == 'Windows' && runner.arch == 'ARM64'
shell: pwsh
working-directory: .
run: ./scripts/setup-windows-arm64.ps1

- name: Show toolchain
run: rustup show active-toolchain

Expand All @@ -62,7 +74,13 @@ jobs:
if: runner.os == 'Windows'
working-directory: .
shell: pwsh
run: ./scripts/test-install-completions.ps1
run: |
./scripts/test-install-completions.ps1
./scripts/test-install-architecture.ps1
& "$env:WINDIR\System32\WindowsPowerShell\v1.0\powershell.exe" -NoProfile -ExecutionPolicy Bypass -File ./scripts/test-install-architecture.ps1
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
& "$env:WINDIR\SysWOW64\WindowsPowerShell\v1.0\powershell.exe" -NoProfile -ExecutionPolicy Bypass -File ./scripts/test-install-architecture.ps1
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }

- name: Setup Node and pnpm
uses: jdx/mise-action@7e36c90d9ab29c415a2384db3006f3ec8a8cc654 # v4.2.4
Expand Down
34 changes: 29 additions & 5 deletions docs/supported-versions.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,13 @@ modifies the client.
match a verified entry in the consumed index.
- `supportedSpotify` is the newest verified Spotify version in that index.
- `classmapFallback` reports whether selection used an older patch.
- `updatesBlocked` reports the installed updater protection at apply time.
- `updatesBlocked` reports native updater protection at apply time. It is
omitted when protection cannot be determined; `false` means the native
updater is known to be unblocked. Managed package updates are separate.
- `managedSpotify` identifies a Spicetify-owned Linux installation and its
package channel, `stable` or `testing`.

Manager combines these local facts with the availability feed. Its
For native installations, Manager combines these local facts with the availability feed. Its
**supported** badge comes from `supportedSpotify`; its **available** badge
comes from the observed-version feed.

Expand Down Expand Up @@ -125,9 +129,29 @@ spicetify spotify-updates unblock
spicetify spotify-updates status
```

Current Windows clients protect the updater staging directory. macOS and Linux
patch the update endpoint in Spotify's binary; macOS also signs the changed app
bundle and applies a secondary update-cache lock.
Current Windows desktop clients protect the updater staging directory.
Microsoft Store updates must be managed through Microsoft Store. macOS patches
the update endpoint in Spotify's binary, signs the changed app bundle, and
applies a secondary update-cache lock.

On Linux, the binary block only works when its expected endpoint is present.
An unrecognized endpoint leaves protection unknown. The Linux managed installer
offers a separate path: `spicetify spotify install` installs a user-owned copy,
and `spicetify spotify update` explicitly downloads and applies a verified
package. System package managers do not own that copy. This does not establish
native updater protection or freeze other Spotify installations.

For managed installations, Manager checks Spotify's Linux package feed and
offers **Update Spotify & Apply** when a newer package has an exact verified
classmap. The daemon owns the job, so closing or restarting the renderer does
not cancel it. It prepares and patches a separate copy before switching the
configuration, desktop entry, and terminal launcher together. Update progress
and the final result remain available after Spotify restarts.

If the daemon itself stops during an update, the next start reports the
interrupted job. Run `spicetify spotify install` to prepare a fresh copy using
the installation's existing channel, then retry. Updates requested from the
terminal use the same installer.

`block` and `unblock` store the user's intent in `config.toml`. A successful
Spotify update can replace the installed protection, so `apply` reasserts a
Expand Down
17 changes: 17 additions & 0 deletions docs/v3-modules.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,23 @@ deliberate act rather than a source the CLI consults on its own:
spicetify pkg install my-module https://example.com/my-module@1.0.0.zip
```

### Updating installed modules

`spicetify pkg update` moves every installed module to the version the
registry currently serves, and `spicetify pkg update <id>` does the same for
one module. It fetches the registry fresh and changes only modules the store
manages. Run `spicetify apply` afterwards; Spotify loads the new versions
then. A bulk update leaves these modules as they are and says why:

- A module you disabled, or pinned by enabling an older version while a newer
one is installed. Naming the module overrides a pin, but not a disable.
- A developer's own directory or linked build.
- A module the registry doesn't carry, or a version installed from an
explicit artifact.

The command fails when any module could not be updated, or when you name a
module it has to leave alone, so a script notices.

`localStorage["spicetify:defaultVaultUrl"]` repoints the store at another
vault. That is a development lever for previewing a catalog before submitting
it, not a distribution channel: it replaces the registry rather than adding
Expand Down
77 changes: 77 additions & 0 deletions docs/windows-arm64-verification.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# Windows ARM64 installation verification, September 29, 2026

The installer selects a matching CLI and daemon for native ARM64 Spotify and
preserves x64 Spotify installations on ARM64 Windows. The native Windows
installation and daemon recovery checks passed with a local release package.
Published ARM64 release downloads remain unverified.

## Environment and artifacts

The native runtime checks used this configuration:

- Windows 11 Pro ARM64, build 26100, in VMware Fusion.
- Desktop Spotify 1.3.1.234, executable machine `0xAA64`.
- Source `2409117615efa9c82341c5aadac6753a60122ee4`, based on beta.19
(`b50800b2d225ca4cc09d83b9e785723c88b9d8af`).
- Rust 1.98.1, native ARM64 MSVC toolchain, Visual Studio Build Tools
17.14.41, MSVC 14.44.35207, and clang-cl 19.1.5.
- Optimized CLI and daemon built together after rebuilding the embedded payload.
- Installed CLI: `C:\Users\test\AppData\Local\spicetify\spicetify.exe`.
- Both installed binaries have PE machine `0xAA64` and report beta.19.
The running daemon version was read from `/health`.

The installed CLI SHA256 is
`A66F66D9CE6515D4835BAC5411D75A83ACED2273CAC739120E1277F0F6D9A0E4`.
The daemon SHA256 is
`452D5AEAC67E5DFF80D4DAD28BB54C7E52B3CEE92D5B1F3A2B8E53E21BF23FD5`.
These are local builds, not published beta.19 assets.

## Installation and lifecycle results

The full installer ran in the regular interactive desktop session. Only the
release archive and checksum responses were replaced with local fixtures.
Checksum verification, extraction, architecture selection, daemon shutdown,
binary replacement, shell completion, and Apply ran through the installer.

| Check | Result |
| --- | --- |
| Native Windows PowerShell 5.1 and 32-bit PowerShell | ARM64 host detected in both; installer regression tests passed |
| Native Spotify | Selected ARM64; installed pair hashes match the optimized build |
| Configured x64 executable fixture | Actual staged ARM64 CLI selected x64; installer fetched the same version's x64 archive and installed both matching binaries |
| Store alias fixture | Unreadable-as-PE alias fell back to the configured data directory's real x64 executable |
| Missing second binary during replacement | Original CLI and daemon restored |
| Locked installed CLI | Replacement rejected; original pair preserved |
| Invalid download checksum | Installation rejected before replacing binaries |
| Enabled daemon stopped before same-version self-update | Real `self-update` restarted it and preserved HKCU Run registration |
| Intentionally disabled daemon | Real `self-update` left it stopped and unregistered |
| Final Apply | Restored normal autostart and URL registration; Spotify restarted normally |

Installer replacement changed the interactive daemon from PID 1904 to 7460.
After the stop/recovery tests and final Apply, PID 4712 ran in session 2 with
both watchers active. The HKCU Run entry points to the installed daemon;
`spicetify://` points to the installed CLI.

Native caption buttons were hidden in the running client. The subsequent
visual pass found that the module still reserved space at the right edge on
Spotify 1.3.1. That CSS selector issue belongs to the modules repository and is
separate from native helper architecture compatibility.

## Build checks and remaining coverage

The macOS workspace suite passed 180 tests, with three ignored. The repository's
CI lint command, `cargo clippy --workspace --locked -- -D warnings`, passed.
A broader `--all-targets` lint run reports test-only lint failures and does not
pass. Nine native Windows update tests and the optimized CLI/daemon build
passed. The ARM64 setup script successfully exported MSVC build variables and
selected the native Rust host in the VM.

The live VM retained a local CSS-map override for the independent fallback
styling fix. Spotify 1.3.1 used the published 1.3.0 classmap fallback. These
substitutions do not establish full classmap compatibility.

The x64 compatibility checks use real x64 PE binaries as fixtures; an actual
x64 Spotify installation was not run. The same-version release-selection test
covers architecture migration, but a complete old published x64 updater to new
published ARM64 release transaction still requires the release assets to exist.
Hosted ARM64 CI and release packaging must pass before release readiness can be
claimed. This work does not enable experimental Windows Spotify updates.
Loading
Loading