7 Commits
Author SHA1 Message Date
Tobias GesellchenandClaude Opus 4.8 720d2abc5c fix(zone): remove a member via /removeZoneSlave instead of a /setZone rebuild (refs #511)
Removing one member from a multi-member zone did nothing. The remove
paths rebuilt the zone with /setZone and the remaining members, but
/setZone is additive: it never drops a member that is simply absent from
the list. It only "removed" when the resulting set was empty (equivalent
to dissolve), which is why removing the last member worked but removing
one of several did not.

Switch all three remove paths to the dedicated /removeZoneSlave endpoint
(already implemented as client.RemoveZoneSlave):

- HandleZoneRemove  (web UI "remove member")
- HandleZoneLeave   (web UI slave "leave zone")
- RemoveFromZone    (client lib, used by CLI `zone remove`)

DissolveZone (setZone master-only) and HandleZoneAdd (additive setZone)
are correct and unchanged. Adds handler regression tests for remove/leave
and rewrites TestClient_RemoveFromZone to assert /removeZoneSlave (the old
test removed one of two members but only checked that setZone was called,
never that the member was dropped).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 20:25:41 +02:00
Tobias GesellchenandClaude Opus 4.8 3a038b0129 refactor(web): move the app-wide socket to /api/control/ws (refs #451)
Move the web UI's app-wide event stream (device list, discovery status,
per-device status updates) from top-level /ws to /api/control/ws. It is
the read/event half of the control surface, so it belongs under the same
namespace as the rest of the web API (the per-device socket already sits
at /api/control/devices/{id}/ws). The bundled app.js WebSocket URL is
updated in lockstep.

This brings soundtouch-web's entire HTTP surface under two clean subtrees
(/api/control/* for the API, /app/* for the SPA), so folding -web into
-service becomes a near-additive mount.

mount_test.go now asserts /api/control/ws is registered and top-level /ws
is gone.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 23:05:59 +02:00
Tobias GesellchenandClaude Opus 4.8 a16b4babcb refactor(web): nest control API under /api/control/* (refs #451)
Restructure soundtouch-web's control API to the post-merge canonical
shape so folding -web into -service later is a near-additive mount.
Device-scoped actions now nest under /api/control/devices/{id}/...,
making every direct child of /api/control a literal namespace (devices,
tunein, radiobrowser, version, discover) with no static-vs-param sibling
ambiguity. Browse/search endpoints (tunein, radiobrowser) stay global.

This is a direct migration (no dual-mount, no deprecation middleware):
-web's only client is its own bundled frontend, so a reload picks up the
new paths. The bundled api.js/app.js are updated in lockstep.

Add mount_test.go: the first test that exercises Mount() itself. It
walks the registered routes to assert (a) registration never panics and
(b) the invariant that every web /api/* route lives under /api/control/*
so no flat route is left behind. Handler unit tests call handlers
directly with injected params, so their request-path literals were
cosmetic; updated to the new nested shape for accurate documentation.

SPA routes and the main /ws socket are unchanged here; they move in
follow-up steps.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 23:05:59 +02:00
Tobias GesellchenandClaude Opus 4.8 8ea2461265 fix(web): forward account param when selecting a source (#444)
The /api/control/{host}/source handler hardcoded an empty sourceAccount,
so devices that share source="AUX" across multiple jacks (e.g. the ST-5
CD/Aux inputs, disambiguated by AUX/AUX1/AUX2) always received
sourceAccount="AUX" and rejected the wrong jack with internal error 1005.

Read the account query parameter and forward it to SelectSource, matching
what the frontend already sends and what the CLI already does.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 21:08:05 +02:00
Tobias GesellchenandClaude Sonnet 4.6 abe9079382 fix(web): strip placeholder SourceAccount before replaying recents
Speakers echo back the source name as SourceAccount when no real
credential is set (e.g. SourceAccount="TUNEIN" for a TUNEIN source).
HandleDevicePlay was forwarding this verbatim, causing the speaker to
try authenticating with the source name as a TuneIn account and
returning INVALID_SOURCE.

Clear SourceAccount when it equals Source; preserve it when it differs
(real credentials such as Spotify or STORED_MUSIC UUIDs).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 15:03:04 +02:00
Tobias Gesellchen b95bdae751 feat: Add RadioBrowser integration alongside TuneIn support
- Refactors BMX service to support multiple radio providers
- Adds RadioBrowser.com API integration with search and browse
- Splits TuneIn logic into separate module for better organization
- Adds new web UI components for radio station discovery
- Includes new SVG icons for RadioBrowser branding
2026-05-18 22:34:26 +02:00
Tobias GesellchenandClaude Opus 4.7 42257aeebc refactor(soundtouch-web): relocate handlers/webtypes to pkg/service/soundtouchweb
Mechanical relocation only — zero semantic change. Sets up the package
layout that the future Preact-UI rewrite (branch `app`) wants, while
preserving every line of main's current logic. Subsequent commits will
land the additive parts (frontend rewrite, recents, zones, bass control)
on top of this clean base.

Moves (`git mv`, content unchanged except package decl):

  cmd/soundtouch-web/handlers/handlers.go      → pkg/service/soundtouchweb/handler.go
  cmd/soundtouch-web/handlers/handlers_test.go → pkg/service/soundtouchweb/handler_test.go
  cmd/soundtouch-web/handlers/websocket.go     → pkg/service/soundtouchweb/websocket.go
  cmd/soundtouch-web/handlers/registry_test.go → pkg/service/soundtouchweb/registry_test.go
  cmd/soundtouch-web/webtypes/types.go         → pkg/service/soundtouchweb/webtypes/types.go
  cmd/soundtouch-web/webtypes/types_test.go    → pkg/service/soundtouchweb/webtypes/types_test.go
  cmd/soundtouch-web/webtypes/status_test.go   → pkg/service/soundtouchweb/webtypes/status_test.go
  cmd/soundtouch-web/static/img/tunein-{dark,mono}.svg → pkg/service/soundtouchweb/static/img/

Adjustments:

- `package handlers` → `package soundtouchweb` in the 4 moved handler-tier
  files (plus their package-doc comments).
- Import paths rewritten in cmd/soundtouch-web/{main.go,spa_test.go} and
  in the moved files themselves: cmd/soundtouch-web/{handlers,webtypes}
  → pkg/service/soundtouchweb/{,webtypes}.
- `handlers.` selector renamed to `soundtouchweb.` in the callers.
- `.golangci.yml` errcheck waiver extended from `cmd/.*\.go` to also
  cover `pkg/service/soundtouchweb/.*\.go`. Same code that the
  cmd-tier waiver applied to; same waiver follows it. Documented as
  a carry-over with the intent to tighten in a follow-up review.

Not changed:

- `cmd/soundtouch-web/main.go` keeps the `//go:embed static` pointing at
  the still-vanilla `cmd/soundtouch-web/static/`. The frontend rewrite
  (Preact UI) lands in a later commit; this one is mechanical.
- `cmd/soundtouch-web/resolve_bind_addr_test.go` stays put — it tests
  main.go-local flag plumbing.

go build ./... clean. go test ./... clean (only pre-existing
TestDocsConsistency fails, untracked-file issue, unrelated).
golangci-lint run ./... 0 issues.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 22:34:26 +02:00