A one-off end-to-end run of the :dev image (scratch Postgres, generated
FLAC files, no outbound network) found that the web restore, done the way
docs/hosting.md describes, was refused with not_empty: a new server's
startup scan fills the library before anyone signs up, and Restore counted
tracks as occupied. The command-line restore passed the same run.
- Restore now asks only that the instance has no accounts. The library the
first scan found is replaced by the backup's (every foreign key into
artists/albums/tracks cascades or sets null), and the restore locks the
library tables so a running scan waits rather than adding to them.
- Settings are read once at boot by about ten services, the library folders
among them, so after a web restore the server ran on its defaults until
restarted. The restore endpoint now answers, then asks the process to
restart in place (syscall.Exec of the same binary, arguments and
environment), which needs no container restart policy. The startup scan
of the restarted server replaces the scan the endpoint used to start.
- The restore form says Minstrel is restarting; the docs say a scanned new
server still takes a backup.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A new server (no accounts, no library) offers "Restore a backup" beside
"Create account". The file goes up as the request body to
POST /api/auth/restore with the setup token from the log in X-Setup-Token,
since the backup's admins become this server's. It loads in one transaction,
then a library scan starts and finds each file at its path. The same load runs
offline as `minstrel restore FILE`, which migrates the schema first.
That path alone gets a 1 GiB, 15-minute body allowance instead of 4 MiB and
30s, and skips the JSON-only check for cookie writes: a stale cookie from the
old server would otherwise refuse the upload, and the custom header already
rules out a cross-site form.
The library sync answers 410 cursor_ahead to a cursor past the last one the
server has handed out, read from the change sequence, so an app synced with
the old server after the backup was made starts over instead of skipping the
ids the restored server reuses. Restore now leaves each sequence as having
issued the restored maximum, so that reading holds; a compacted log still
counts its handed-out cursors.
backup.Read keeps the underlying error in its chain, so an oversized upload
answers 413 rather than "not a backup". The hosting docs say how to put the
music back first and what is entered again afterwards.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Rewrites the web app's comments to rule 32. Task, milestone, rule and
spec ids are gone; regression stories are restated as the trap they
guard (public auth routes, the menu's stopPropagation, universal loads
under adapter-static, metric deltas with their margin). Flutter and
Tauri mentions now name the Android app or a native shell, operator
quotes and dates are dropped, and one library's counts no longer
appear outside test fixtures. Verified with a TypeScript-scanner token
comparison (template literals and regex literals re-scanned): only
comment text changed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Merges Renovate's kit 3 (PR #149) and adapter-static 4 (PR #148) bumps,
plus the migration they need. The mechanical part is `sv migrate
sveltekit-3`, run one task at a time and reviewed:
- svelte.config.js is gone. Its options move into sveltekit() in
vite.config.ts, exported as kitOptions so vitest.config.ts runs the
same kit setup, including the $test-utils alias the tests import.
- $lib becomes #lib through package.json "imports". There is no
src/lib/index, so only the "#lib/*" entry is kept.
- tsconfig extends $app/tsconfig.
- Peer floors raised to kit 3's requirements: svelte ^5.57.1, vite
^8.0.12, svelte-check ^4.7.5.
By hand, from the codemod's list of non-automated tasks:
- goto's replaceState option is now replace; keepFocus becomes
reset: false. For the search typeahead, reset: false also stops the
scroll-to-top, which is wanted while typing.
- The test setup mocks drop pushState/replaceState and $app/paths
base/assets, which kit 3 removed, and mock refreshAll in place of
invalidateAll.
- The other flagged files only read page.url or goto internal routes,
so they needed no change.
TypeScript goes to ^6, not the ^7 Renovate offers: kit 3 declares
typescript ^6 as a peer and svelte-check 4.7 accepts ^5 || ^6. Move to
7 once both accept it.
With Tailwind 4 and kit 3 in, `npm audit` on the whole tree reports 0,
so the web lane now audits every dependency rather than only what
ships to browsers.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
While no accounts exist, the server mints a random setup token at boot and
logs it. Registering the first account (which becomes admin) must carry it,
so whoever reaches a freshly exposed instance first cannot claim it. The
register page asks GET /api/auth/setup-status and shows a "Setup token"
field in place of the invite field while setup is pending.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Web client posts Intl.DateTimeFormat().resolvedOptions().timeZone to
PUT /api/me/timezone after every successful login, register, and
bootstrap when the locally-stored tz_last_sent_at is >7 days old.
Cadence tracked in localStorage keeps the server stateless on the
"is this stale?" check.
Failures swallowed: the server's UTC default + last-known value keep
the scheduler functioning until the next successful attempt. SSR-
safe via the typeof window guard.
For #392 Half B. Companion Flutter change in next commit.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
auth/store.svelte.ts no longer imports from $lib/player. Logout
broadcasts via auth/sessionEnd.svelte.ts (a tiny tick + outgoing
userId pair); player subscribes through $effect.root and owns the
player-specific teardown (clearPersistedQueue + playQueue([]) +
closeQueueDrawer()).
Cycle fully inverted — auth is now a pure leaf for player; future
session-end consumers (download cache, recent-search history) plug
in by adding their own effect on the same signal.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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).
Public /register form: username, optional display name, password +
confirm, optional invite token. Submits to POST /api/auth/register
via the new register() helper in auth/store.svelte.ts. On success
the server sets the session cookie and the frontend redirects to /;
on error the page shows a code-specific message (invite_required,
invite_invalid, username_taken, etc.).
Login page gets a "Don't have an account? Register" link below the
form for symmetry.
Tests cover form rendering, password-mismatch client-side rejection,
the happy-path submit + redirect, and the invite-invalid error
mapping.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Central synchronous source of truth for 'who is signed in'. bootstrap()
runs once in +layout.ts load(); login/logout mutate _user and the
TanStack query cache. The silent option on logout is used by the 401
interceptor (next commit) to avoid POSTing /logout against an already-
invalid session.