Commit Graph

16 Commits

Author SHA1 Message Date
bvandeusen 4c4399c9bb feat(server,web): expose server version via /healthz + display in Settings
Came up debugging the in-app update flow — wasn't obvious which image
the container was actually running without exec'ing in. New flow:

### Server
- internal/server/version.go: new `var ServerVersion = "dev"`,
  overridden via -ldflags at build time.
- /healthz response gains a "version" key alongside the existing
  "status" + "min_client_version". Backward-compat: existing clients
  ignore unknown JSON fields. Endpoint stays unauthenticated.

### Build
- Dockerfile: new `ARG MINSTREL_VERSION=dev`, threaded into the
  go build -ldflags so the binary's ServerVersion is stamped at
  link time. Default "dev" preserves local `docker build` ergonomics.
- .forgejo/workflows/release.yml: tags step also emits a `version`
  output (the git tag for tag pushes, "main" for branch pushes);
  build step passes it as `--build-arg MINSTREL_VERSION=...`.

### Web
- web/src/lib/components/ServerVersion.svelte: small understated
  text ("Server v2026.05.10.2") that fetches /healthz on mount.
  Renders nothing on parse failure or pre-version images.
- Mounted at the bottom of Settings.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 23:13:19 -04:00
bvandeusen 5054c46385 fix(web): collapse the entire Mobile app section when no APK bundled
When /api/client/version returns 404 (no APK in /app/client/, e.g.
v2026.05.10.0 which had a failed CI APK-attach step), the previous
shape rendered the section heading + intro paragraph with an empty
hole where the button should be. Confusing — looks like a broken
button.

Moved the section wrapper, heading, and intro paragraph INSIDE
MobileAppDownload so the whole block collapses together when info
is null. Settings page just mounts <MobileAppDownload />; nothing
visible if no APK is available.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 22:44:38 -04:00
bvandeusen 2435ef7961 feat(web): MobileAppDownload component on login + settings (#397 follow-up)
Closes the discoverability gap on the in-app update flow — the
/api/client/apk endpoint exists but had no web UI surface to find
it. User asked to be able to "visit the site on my phone and
download the apk from there."

- web/src/lib/components/MobileAppDownload.svelte — fetches
  /api/client/version once on mount; renders a download link with
  version + size when 200; renders nothing on 404 (no APK bundled,
  graceful degradation in dev environments and pre-CI-wiring images).
- Mounted on the login page (below the Register link) so the link
  is discoverable without authentication. The /api/client/* endpoints
  are themselves unauthed, so the flow works end-to-end for any
  visitor on a phone.
- Also mounted in Settings → Mobile app section for logged-in users
  who want to grab the matching APK for sideloading on a different
  device.

Browser handles the download via the server's existing
Content-Disposition: attachment; filename="minstrel.apk" header.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 20:23:56 -04:00
bvandeusen 3e34f1f7b3 refactor(web/test): default svelte-query mock in vitest.setup; remove from 34 files (B3) 2026-05-08 11:08:41 -04:00
bvandeusen 617477b702 refactor(web): pushToast store + ToastHost; 8 sites migrated (W3) 2026-05-08 05:45:34 -04:00
bvandeusen 9c91a342e2 refactor(web/api): errCode + errMessage helpers; 41 sites migrated (W1) 2026-05-07 22:17:45 -04:00
bvandeusen e4ebc71162 fix(ci): mailer + audit lint + settings Save button collision
Three Go lint hits + two web test failures, all from the U3 push.

- internal/mailer/mailer.go: SentEmail.HtmlBody → HTMLBody (Go's
  initialism convention; revive flagged); FakeSender.Send unused
  ctx parameter renamed to _.
- internal/audit/audit_test.go: removed dead validUUID helper. It
  was added speculatively in U1-T1 and never called by any test.
  pgtype import stays — other tests use it.
- web/src/routes/settings/settings.test.ts: existing ListenBrainz
  tests queried `getByRole('button', { name: /save/i })` which
  worked when the page had only one Save button. The new Profile
  card adds a "Save profile" button that also matches the regex,
  triggering "found multiple elements". Anchored to /^save$/i for
  exact-match. The newer Save profile tests still use
  /save profile/i which is unique.

This is the same shape of test-fixture-lag I owe an answer for: I
landed new content that broke an existing test, and the existing
test had a too-loose selector. The right fix is to tighten the old
selector now (this commit) and to flag this pattern — selectors
that regex-match by partial words — as a candidate for the DRY
pass / test-utils consolidation.
2026-05-07 18:15:39 -04:00
bvandeusen 444144038a fix(ci): library_test Mount arg + 4 web test selectors + forgot-pw catch
Five fixes in one commit, all from the U1+U2+U3 push:

1. internal/api/library_test.go's Mount call wasn't updated when
   U3-T4 added the mailer.Sender param. 17 args, sig wants 18.
   Adds a trailing nil for the mailer. Same shape of test-fixture
   lag the project has hit before — flagged as a recurring pattern
   for the DRY-pass slice.

2. web/src/routes/settings/settings.test.ts mocked $lib/api/me with
   `getAPIToken: vi.fn()` (no return value). The new /settings API
   Token card's $effect calls `getAPIToken().then(...)` which threw
   "Cannot read properties of undefined (reading 'then')" on every
   ListenBrainz test. Default the mock to mockResolvedValue so any
   test that doesn't override gets a valid resolution.

3. web/src/routes/admin/users/users.test.ts queried row buttons by
   /^Delete$/ but T3 added per-user aria-labels ("Delete alice"),
   so the row buttons' accessible name is no longer "Delete". The
   modal's confirm button has no aria-label so its name IS "Delete"
   exactly. Tests now click by per-user aria-label first, then by
   /^Delete$/ for the modal confirm.

4. Same file: /Set password/i regex matched both the dialog's
   submit button AND the row's "Reset password for ..." buttons
   (because regex `Set` matches "ReSet" case-insensitively). Switched
   to /^Set password$/ exact-match.

5. web/src/routes/reset-password/[token]/reset-password.test.ts had
   /New password/i which matched both the "New password" and
   "Confirm new password" labels. Switched to exact-match string
   selectors.

6. web/src/routes/forgot-password/+page.svelte's onSubmit handler
   had no catch — a rejected forgotPassword() bubbled as an
   unhandled rejection in vitest. Added a silent catch since the
   page intentionally shows the same success message regardless of
   outcome (no-enumeration posture).
2026-05-07 18:05:30 -04:00
bvandeusen 5da2c85f70 feat(web/m7-user-mgmt): /reset-password page + admin SMTP card + tests (U3-T5b)
Completes the U3 frontend that T5a's crashed dispatch left half-done.

- /reset-password/[token] — public; new password + confirm. Calls
  POST /api/auth/reset-password with the URL's token. invalid_token,
  password_too_short, and mismatched-passwords surface as inline
  errors. Success redirects to /login?reset=ok.
- /admin/integrations gains an SMTP card (alongside Lidarr) with
  enabled toggle + all fields + Save and Send-test-email buttons.
  Password input is a separate state from the loaded form, so empty
  submission preserves the stored server-side value (matches the
  backend's empty-password-preserves contract). Test button error
  codes (no_email_on_file / not_configured / send_failed) surface
  as actionable toasts.

Tests cover both new pages plus extensions to settings + integrations
test files for the cards landed in T5a.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-07 17:43:47 -04:00
bvandeusen 72f46a885b feat(web/m7-user-mgmt): /settings expansion + /forgot-password (U3-T5a)
Lands four of the U3 frontend surfaces. Splits T5 because the
dispatch covering everything in one shot crashed the runtime
mid-execution; this is the salvageable half.

- /settings gains three cards alongside Appearance: Profile
  (display name + email), Password (current + new + confirm with
  client-side mismatch check), API Token (display + copy +
  regenerate-with-double-click-confirm). Server error codes map
  to clear toasts.
- /forgot-password — public; takes an email and always shows the
  success message regardless of whether the email is on file
  (mirrors the server's no-enumeration posture).
- Login page gets a "Forgot password?" link below the existing
  register link.
- API client functions for the four /me endpoints (changePassword,
  updateProfile, getAPIToken, regenerateAPIToken), the SMTP admin
  trio (getSMTPConfig, updateSMTPConfig, testSMTPConfig), and the
  forgot/reset auth pair (forgotPassword, resetPassword).

Tests for these surfaces + /reset-password page + admin SMTP card
land in T5b (next follow-up).
2026-05-07 17:33:48 -04:00
bvandeusen 66f651cf5c fix(web/m7-362): repair Vitest fixtures (matchMedia.fire, listenbrainz mock)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-03 18:48:20 -04:00
bvandeusen 9ad2e0343a feat(web/m7-363): per-route svelte:head titles for static routes
Add browser tab titles to all 18 static page routes using the pageTitle()
helper from $lib/branding. Titles follow the Minstrel · Section format.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 17:37:13 -04:00
bvandeusen 16a692e81a test(web/m7-362): Settings Appearance card render + selection test 2026-05-03 13:53:47 -04:00
bvandeusen e40fa0d252 feat(web/m7-362): Settings → Appearance card with dark/light/system selector 2026-05-03 13:51:25 -04:00
bvandeusen a598ea1418 feat(web): move Hidden tracks out of main nav, surface from Settings
Hidden tracks (M5b quarantine) is a low-frequency surface — it doesn't
warrant a permanent slot in the main nav. Moves the link under a new
'Library' section card on /settings alongside ListenBrainz config.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 21:14:01 -04:00
bvandeusen 13e12d97ae feat(web): add /settings page with ListenBrainz section
Adds api.put helper, ListenBrainz API module with query/mutation
helpers, a /settings route with token + enable/disable UI, and 5
tests. Adds Settings to the Shell nav.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 15:45:42 -04:00