diff --git a/web/csp.go b/web/csp.go
index 7ac77736..054f2cea 100644
--- a/web/csp.go
+++ b/web/csp.go
@@ -20,7 +20,7 @@ var srcAttrRe = regexp.MustCompile(`(?i)\bsrc\s*=`)
// page carries: the theme bootstrap in app.html, the branding global the Vite
// plugin injects, and SvelteKit's start-up block. Their hashes are taken from
// the page AFTER the branding template has run, so they match the bytes the
-// browser actually receives, whatever the operator's app name. Any script
+// browser actually receives, whatever the app is named. Any script
// that differs, including one injected through an XSS, is refused.
//
// The rest:
diff --git a/web/csp_test.go b/web/csp_test.go
index b603597a..52e2d725 100644
--- a/web/csp_test.go
+++ b/web/csp_test.go
@@ -49,7 +49,7 @@ func TestContentSecurityPolicy_HashesInlineScriptsOnly(t *testing.T) {
}
// The hash must be of the page as served, after the branding template has
-// substituted the operator's app name, or a renamed instance would refuse
+// substituted the app's name, or a renamed instance would refuse
// its own bootstrap script.
func TestContentSecurityPolicy_TracksTemplatedContent(t *testing.T) {
a := contentSecurityPolicy([]byte(``))
diff --git a/web/scripts/check-tint-contrast.js b/web/scripts/check-tint-contrast.js
index 5d6af080..60d99493 100644
--- a/web/scripts/check-tint-contrast.js
+++ b/web/scripts/check-tint-contrast.js
@@ -1,4 +1,4 @@
-// Finds a hue painted as TEXT on a tint of itself (#3150). The tint sits close
+// Finds a hue painted as TEXT on a tint of itself. The tint sits close
// to the surface under it, so the raw hue on it measures 1.6–2.4:1 against
// AA's 4.5. The fix is the hue's -fg partner (tokens.json colors.fg), mixed
// toward parchment: accent-fg on an accent tint measures 5.03.
@@ -67,7 +67,7 @@ export function findCss(source) {
// Hues that fail AA as text even on a plain surface, so the raw hue is never
// text at all. Dark mode, against 4.5:
// error 2.64 on slate, 3.23 on iron → error-fg 5.30 at worst
-// accent 2.21 on slate, 3.04 on the page → accent-fg 5.62 at worst (#5318)
+// accent 2.21 on slate, 3.04 on the page → accent-fg 5.62 at worst
// Accent icons (the liked heart, active shuffle/repeat) follow too: as
// graphics they only need 3:1, and the raw accent misses even that on iron.
// A border or outline in the hue is a graphic and stays allowed.
diff --git a/web/scripts/tokens-to-css.js b/web/scripts/tokens-to-css.js
index 42495359..527ed6a1 100644
--- a/web/scripts/tokens-to-css.js
+++ b/web/scripts/tokens-to-css.js
@@ -1,8 +1,8 @@
#!/usr/bin/env node
-// Reads ../src/lib/styles/tokens.json and emits tokens.generated.css.
-// Single source of truth for FabledSword tokens shared with the Flutter
-// client. Keep output deterministic — Tailwind reads tokens.json directly,
-// the .css emission is for raw CSS consumers and theme switching.
+// Reads ../src/lib/styles/tokens.json and emits tokens.generated.css. Single
+// source of truth for the FabledSword tokens, which the Android app mirrors in
+// FabledSwordTokens.kt. Keep output deterministic — Tailwind reads tokens.json
+// directly, the .css emission is for raw CSS consumers and theme switching.
import { readFileSync, writeFileSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import { dirname, resolve } from 'node:path';
@@ -19,7 +19,7 @@ export function emit(tokens) {
for (const [name, value] of Object.entries(tokens.colors.flat)) {
lines.push(` --fs-${name}: ${value};`);
}
- // The house -fg tokens (FabledSword, 2026-08-27; #3150): a hue as TEXT on a
+ // The house -fg tokens: a hue as TEXT on a
// tint of itself, mixed toward parchment. Parchment inverts in light mode,
// so one declaration lightens the text on dark surfaces and darkens it on
// light ones. Declared on :root, the element data-theme is set on, so the
diff --git a/web/src/lib/admin/queues.ts b/web/src/lib/admin/queues.ts
index 767831a7..7232b02d 100644
--- a/web/src/lib/admin/queues.ts
+++ b/web/src/lib/admin/queues.ts
@@ -1,11 +1,10 @@
import { plural } from '#lib/utils/plural.js';
import type { AdminQueue, AdminSummary } from '#lib/api/types.js';
-// The queues that wait on an admin (#5605), in the order the Overview lists
+// The queues that wait on an admin, in the order the Overview lists
// them. The rails and the Overview read the same list, so a queue that
// needs you is counted the same in every place. Requests live under Admin;
-// the library's health queues, Missing files among them, under Collection
-// (M504).
+// the library's health queues, Missing files among them, under Collection.
export type QueueKey = keyof AdminSummary;
export type QueueDef = {
diff --git a/web/src/lib/admin/reports.ts b/web/src/lib/admin/reports.ts
index 3720c80e..7332b7ff 100644
--- a/web/src/lib/admin/reports.ts
+++ b/web/src/lib/admin/reports.ts
@@ -7,7 +7,7 @@ import type {
PlaybackErrorKind
} from '#lib/api/types.js';
-// Admin › Reports (#5606): a broken track is one thing to decide about,
+// Admin › Reports: a broken track is one thing to decide about,
// whether a listener flagged it or it failed to play, so the two sources
// fold into one row per track. The rail's count (the summary's `reports`)
// counts the same way, by track.
diff --git a/web/src/lib/api/admin.ts b/web/src/lib/api/admin.ts
index 8e71dfb1..058ee980 100644
--- a/web/src/lib/api/admin.ts
+++ b/web/src/lib/api/admin.ts
@@ -21,7 +21,7 @@ import type {
PlaybackErrorResolution
} from './types';
-// Admin summary (#5605) ---------------------------------------------------
+// Admin summary ---------------------------------------------------
export async function getAdminSummary(): Promise {
return api.get('/api/admin/summary');
@@ -38,7 +38,7 @@ export function createAdminSummaryQuery() {
});
}
-// Admin status (M504): who is listening, downloads, the library and disk,
+// Admin status: who is listening, downloads, the library and disk,
// and the server, for the Overview below its verdict. Polled while open.
export async function getAdminStatus(): Promise {
@@ -68,7 +68,7 @@ export async function listAdminRequests(
);
}
-// Approving puts the request on the wanted list (M502 #5520). An artist
+// Approving puts the request on the wanted list. An artist
// request is followed on profileId, or the default profile when omitted.
export async function approveRequest(id: string, profileId?: string): Promise {
return api.post(
@@ -93,7 +93,7 @@ export function createAdminRequestsQuery(status?: RequestStatus) {
queryFn: () => listAdminRequests(status),
// Only the 'approved' tab needs polling — that's where in-flight
// ingests live. Other tabs (pending/rejected/completed) are static
- // until the operator acts on them.
+ // until an admin acts on them.
refetchInterval: (query) => {
if (status !== 'approved') return false;
const rows = query.state.data as MusicRequest[] | undefined;
@@ -280,14 +280,14 @@ export function createCoverageQuery() {
});
}
-// Fingerprint backfill (#3908) --------------------------------------------
+// Fingerprint backfill --------------------------------------------
export type FingerprintCoverage = {
total: number;
fingerprinted: number;
rejected: number;
pending: number;
- // False when the operator has switched fingerprinting off (#3913): pending
+ // False when an admin has switched fingerprinting off: pending
// then never shrinks, and nothing should read as progress.
enabled: boolean;
};
@@ -307,7 +307,7 @@ export function createFingerprintCoverageQuery() {
});
}
-// Fingerprinting settings (#3913) ------------------------------------------
+// Fingerprinting settings ------------------------------------------
export type FingerprintSettings = {
enabled: boolean;
@@ -317,7 +317,7 @@ export type FingerprintSettings = {
acoustic_max_bit_error_rate: number;
backfill_concurrency: number;
sweep_interval_hours: number;
- // Lets the duplicate resolver act on its own (M498): merge copies no
+ // Lets the duplicate resolver act on its own: merge copies no
// release needs.
auto_resolve: boolean;
};
@@ -332,7 +332,7 @@ export async function updateFingerprintSettings(
return api.put('/api/admin/library/fingerprint-settings', s);
}
-// Loudness analysis (#4995) -------------------------------------------------
+// Loudness analysis -------------------------------------------------
export type LoudnessCoverage = {
total: number;
@@ -342,7 +342,7 @@ export type LoudnessCoverage = {
silent: number;
unreadable: number;
pending: number;
- // False when the operator has switched analysis off: pending then never
+ // False when an admin has switched analysis off: pending then never
// shrinks, and nothing should read as progress.
enabled: boolean;
};
@@ -354,7 +354,7 @@ export async function getLoudnessCoverage(): Promise {
export type LoudnessSettings = {
enabled: boolean;
backfill_concurrency: number;
- /** Disk for the leveled copies rendered for Sonos/UPnP speakers (#5001). */
+ /** Disk for the leveled copies rendered for Sonos/UPnP speakers. */
leveled_cache_mb: number;
};
@@ -366,7 +366,7 @@ export async function updateLoudnessSettings(s: LoudnessSettings): Promise('/api/admin/library/loudness-settings', s);
}
-// The library's folders (#6129) -----------------------------------------------
+// The library's folders -----------------------------------------------
export type LibraryFolder = {
path: string;
@@ -395,7 +395,7 @@ export async function updateLibraryFolders(folders: LibraryFolderChange[]): Prom
return api.put('/api/admin/library/folders', { folders });
}
-// AcoustID recording-id lookup (M401) ---------------------------------------
+// AcoustID recording-id lookup ---------------------------------------
export type AcoustIDSettings = {
enabled: boolean;
@@ -529,7 +529,7 @@ export function createCoverProvidersQuery() {
// Tag-enrichment providers -------------------------------------------------
// Parallel to the cover-art providers surface, over /api/admin/tag-sources.
// Independent settings so a new folksonomy source is added without touching
-// art config (#1490).
+// art config.
export type TagProviderCapability = 'track_tags';
@@ -656,7 +656,7 @@ export async function updateUserAutoApprove(id: string, autoApprove: boolean): P
return api.put(`/api/admin/users/${id}/auto-approve`, { auto_approve: autoApprove });
}
-// Flip an account's diagnostics/debug-reporting opt-in (M9). Admin-set;
+// Flip an account's diagnostics/debug-reporting opt-in. Admin-set;
// the client obeys it (with a local per-device OFF switch).
export async function updateUserDebugMode(id: string, enabled: boolean): Promise {
return api.put(`/api/admin/users/${id}/debug-mode`, { enabled });
@@ -709,7 +709,7 @@ export function createSMTPConfigQuery() {
});
}
-// Device diagnostics (M9) ---------------------------------------------------
+// Device diagnostics ---------------------------------------------------
// Coarse event category. The finer event sub-type lives inside `payload`.
export type DiagnosticKind =
@@ -773,7 +773,7 @@ export function createAdminDiagnosticsQuery(f: DiagnosticsFilter) {
return createQuery({
queryKey: qk.adminDiagnostics(f as Record),
queryFn: () => listAdminDiagnostics(f),
- // The operator enables debug then watches events stream in; a short
+ // An admin enables debug then watches events stream in; a short
// poll keeps the timeline live without manual refresh.
refetchInterval: 10_000,
staleTime: 5_000
@@ -788,7 +788,7 @@ export function createDiagnosticDevicesQuery(userId?: string) {
});
}
-// Trusted-proxy depth (#2453) ---------------------------------------------
+// Trusted-proxy depth ---------------------------------------------
// detected_client_ip / forwarded_chain / remote_addr describe THIS request
// under the current setting, so the admin card can be verified rather than
@@ -823,7 +823,7 @@ export async function updatePublicUrl(publicUrl: string): Promise= last.total ? undefined : loaded;
}
-// Loads as the page scrolls (rule 172), one tab at a time. Polls so a sweep
-// or resolver pass finishing shows up while the operator is on the page.
+// Loads as the page scrolls, one tab at a time. Polls so a sweep
+// or resolver pass finishing shows up while an admin is on the page.
export function createDuplicatesQuery(view: DuplicatesView = 'review') {
return createInfiniteQuery({
queryKey: qk.adminDuplicates(view),
@@ -866,7 +866,7 @@ export function resolvedDuplicatesNextOffset(last: ResolvedDuplicatesResponse):
return last.items.length === 0 || loaded >= last.total ? undefined : loaded;
}
-// What the resolver did by itself (M498), newest first.
+// What the resolver did by itself, newest first.
export function createResolvedDuplicatesQuery() {
return createInfiniteQuery({
queryKey: qk.adminResolvedDuplicates(),
@@ -881,9 +881,9 @@ export async function runDuplicateSweep(): Promise<{ started: boolean }> {
return api.post<{ started: boolean }>('/api/admin/library/duplicates/sweep', {});
}
-// Merges a group into the copy chosen to keep, removing the other copies' files.
-// survivor_track_id is the operator's choice; the server checks it belongs to
-// the group.
+// Merges a group into the copy chosen to keep, removing the other copies'
+// files. survivor_track_id is an admin's choice; the server checks it belongs
+// to the group.
export async function mergeDuplicateGroup(
id: string,
body: { survivor_track_id: string }
@@ -894,7 +894,7 @@ export async function mergeDuplicateGroup(
);
}
-// One recording in several of an album's slots (#6184): keep the copy whose
+// One recording in several of an album's slots: keep the copy whose
// title it is, or none (empty keep_track_id), and fetch the album again.
export async function refetchDuplicateGroup(
id: string,
@@ -910,7 +910,7 @@ export async function dismissDuplicateGroup(id: string): Promise {
await api.post(`/api/admin/library/duplicates/${encodeURIComponent(id)}/dismiss`, {});
}
-// Missing files (#2527) -----------------------------------------------------
+// Missing files -----------------------------------------------------
export async function listMissingFiles(
offset: number = 0,
@@ -936,7 +936,7 @@ export function createMissingFilesQuery(offset: number = 0, limit: number = 50)
});
}
-// Suspect sources (#5410) --------------------------------------------------
+// Suspect sources --------------------------------------------------
export const SUSPECT_SOURCES_PAGE_SIZE = 50;
@@ -952,7 +952,7 @@ export function suspectSourcesNextOffset(last: AdminSuspectResponse): number | u
return loaded >= last.total ? undefined : loaded;
}
-// Loads as the page scrolls (rule 172). Changes only when files are added or
+// Loads as the page scrolls. Changes only when files are added or
// removed, so it is not re-fetched on every focus.
export function createSuspectSourcesQuery() {
return createInfiniteQuery({
@@ -965,12 +965,12 @@ export function createSuspectSourcesQuery() {
}
// 'fine' lets a flagged track back into radio and the mixes; 'suspect' holds
-// it back again (M498 #5439).
+// it back again.
export async function setSuspectVerdict(trackId: string, verdict: SuspectVerdict): Promise {
await api.put(`/api/admin/library/suspect-sources/${encodeURIComponent(trackId)}`, { verdict });
}
-// Missing-file re-acquisition (#2527 / milestone #290) ----------------------
+// Missing-file re-acquisition ----------------------
export type ReacquisitionSettings = {
enabled: boolean;
@@ -992,7 +992,7 @@ export async function updateReacquisitionSettings(
return api.put('/api/admin/library/reacquisition', s);
}
-// Notification emails (M489 #5346): when the grouped emails go out.
+// Notification emails: when the grouped emails go out.
export type NotificationEmailSettings = {
summary_hour: number;
batch_window_minutes: number;
@@ -1008,7 +1008,7 @@ export async function updateNotificationEmailSettings(
return api.put('/api/admin/notification-email', s);
}
-// Indexers and download clients (M502 #5518) -------------------------------
+// Indexers and download clients -------------------------------
export type AcquisitionProtocol = 'prowlarr' | 'torznab' | 'qbittorrent' | 'exchange';
diff --git a/web/src/lib/api/catalogue.ts b/web/src/lib/api/catalogue.ts
index 8729a85a..09d66f43 100644
--- a/web/src/lib/api/catalogue.ts
+++ b/web/src/lib/api/catalogue.ts
@@ -3,8 +3,8 @@ import { api } from './client';
import { qk } from './queries';
import type { CatalogueSearchResult, RequestKind } from './types';
-// searchCatalogue asks MusicBrainz through Minstrel's release catalogue
-// (M502 #5519). URLSearchParams handles encoding (spaces, quotes,
+// searchCatalogue asks MusicBrainz through Minstrel's release catalogue.
+// URLSearchParams handles encoding (spaces, quotes,
// ampersands) so callers can pass q raw.
export async function searchCatalogue(
q: string,
diff --git a/web/src/lib/api/errors.ts b/web/src/lib/api/errors.ts
index 3a2f5858..6b77147e 100644
--- a/web/src/lib/api/errors.ts
+++ b/web/src/lib/api/errors.ts
@@ -9,20 +9,20 @@ export function errCode(err: unknown): string {
}
/**
- * Codes whose server message carries specifics the operator needs in order to
+ * Codes whose server message carries specifics an admin needs in order to
* act — which directory, which uid — that fixed copy cannot say. For these the
- * message follows the copy (#3918). Kept to a named list on purpose: most
+ * message follows the copy. Kept to a named list on purpose: most
* server messages are internal detail and must never reach a toast. Mirrored
* in Android's ErrorCopy.
*/
const DETAIL_CODES: ReadonlySet = new Set([
'library_not_writable',
'file_delete_failed',
- // The server names the field and its range (#3913).
+ // The server names the field and its range.
'invalid_setting',
- // The server says what shape of address it wants (#4981).
+ // The server says what shape of address it wants.
'invalid_public_url',
- // The server names the folder and what is wrong with it (#6129).
+ // The server names the folder and what is wrong with it.
'bad_folders'
]);
diff --git a/web/src/lib/api/me.ts b/web/src/lib/api/me.ts
index d2217c2c..2a9fdb96 100644
--- a/web/src/lib/api/me.ts
+++ b/web/src/lib/api/me.ts
@@ -43,7 +43,7 @@ export async function changePassword(currentPassword: string, newPassword: strin
}
// The signed-in user's profile, to fill the Account form with what is
-// stored (#5596): a form that starts blank sends blanks, and a blank clears.
+// stored: a form that starts blank sends blanks, and a blank clears.
export async function getMyProfile(): Promise {
return api.get('/api/me');
}
@@ -61,7 +61,7 @@ export async function regenerateAPIToken(): Promise {
return api.post('/api/me/api-token', {});
}
-// Subsonic password (#5026) ------------------------------------------------
+// Subsonic password ------------------------------------------------
// For Subsonic clients that only sign in with a username and password (the
// t/s scheme). The server generates it, so it is never the login password;
// like the API key it is shown once, when generated.
@@ -89,7 +89,7 @@ export async function putMyTimezone(timezone: string): Promise {
await api.put('/api/me/timezone', { timezone });
}
-// Active sessions (#370) ---------------------------------------------------
+// Active sessions ---------------------------------------------------
// created_ip is frozen at issue time; last_ip moves with the session. The
// pair is the signal — the same device string arriving from an address you
diff --git a/web/src/lib/api/metrics.ts b/web/src/lib/api/metrics.ts
index 776ee52b..c8e465ea 100644
--- a/web/src/lib/api/metrics.ts
+++ b/web/src/lib/api/metrics.ts
@@ -3,8 +3,8 @@ import { api } from './client';
// Mirrors internal/api/me_recommendation_metrics.go: raw play sources are
// bucketed server-side into stable surface families, grouped by intent, and
-// anchored by the manual-plays baseline (milestone #127).
-// A difference from the baseline, with its uncertainty (#2495). Both figures
+// anchored by the manual-plays baseline.
+// A difference from the baseline, with its uncertainty. Both figures
// are already in percentage points — the server does the arithmetic so both
// clients read the same numbers.
//
@@ -31,7 +31,7 @@ export type SurfaceMetric = {
skip_delta?: MetricDelta;
completion_delta?: MetricDelta;
// Present when the surface's builder stamps pick-kind provenance and
- // the window holds attributed plays (#1249, generalized #1270): For
+ // the window holds attributed plays: For
// You's taste/fresh split, Discover's candidate buckets, the tiered
// mixes' tier1-3 — plus pre-attribution plays.
breakdown?: SurfaceMetric[];
diff --git a/web/src/lib/api/normalization.ts b/web/src/lib/api/normalization.ts
index 9c7db924..e9786b16 100644
--- a/web/src/lib/api/normalization.ts
+++ b/web/src/lib/api/normalization.ts
@@ -1,6 +1,6 @@
import { api, apiFetch } from './client';
-// Loudness normalization preference (M464 #4998). One per user, stored on
+// Loudness normalization preference. One per user, stored on
// the server so the web player, the Android app and casts agree.
export type NormalizationMode = 'off' | 'auto' | 'track' | 'album';
export type NormalizationTarget = -18 | -16 | -14;
@@ -41,7 +41,7 @@ export function putNormalization(p: NormalizationPrefs): Promise('/api/me/normalization', p);
}
-// Per-track gains (#4997): dB to the -18 LUFS ReplayGain reference and linear
+// Per-track gains: dB to the -18 LUFS ReplayGain reference and linear
// peaks. A field is absent until the server has measured it.
export type ReplayGain = {
track_gain?: number;
diff --git a/web/src/lib/api/notifications.ts b/web/src/lib/api/notifications.ts
index 1b283086..b2990852 100644
--- a/web/src/lib/api/notifications.ts
+++ b/web/src/lib/api/notifications.ts
@@ -2,7 +2,7 @@ import { createQuery } from '@tanstack/svelte-query';
import { api } from './client';
import { qk } from './queries';
-// The notifications inbox (M489). The server renders title, body and link per
+// The notifications inbox. The server renders title, body and link per
// kind, so every client says the same thing and this module never has to know
// what a kind means.
@@ -123,7 +123,7 @@ export function createNotificationSettingsQuery() {
});
}
-/** Short row labels, menu-style (rule 188). Unknown kinds show their key. */
+/** Short row labels, menu-style. Unknown kinds show their key. */
export const NOTIFICATION_KIND_LABELS: Record = {
request_approved: 'Request approved',
request_rejected: 'Request declined',
diff --git a/web/src/lib/api/playlists.ts b/web/src/lib/api/playlists.ts
index bd340d74..c3dddfd6 100644
--- a/web/src/lib/api/playlists.ts
+++ b/web/src/lib/api/playlists.ts
@@ -19,20 +19,19 @@ export async function getPlaylist(id: string): Promise {
return api.get(`/api/playlists/${encodeURIComponent(id)}`);
}
-// #415 stage 2/3: rotation-aware play order for a system playlist.
+// Rotation-aware play order for a system playlist.
// Same shape as getPlaylist but tracks are server-ordered (unplayed
// this rotation first, then heard; resets when exhausted).
// Intentionally uncached — it varies per play, unlike getPlaylist.
-// {variant} is the raw system_variant (#411 R2: generic by-kind).
+// {variant} is the raw system_variant.
export async function systemShuffle(variant: string): Promise {
return api.get(
`/api/playlists/system/${encodeURIComponent(variant)}/shuffle`,
);
}
-// #411 R2: generic by-kind refresh. Rebuilds the caller's system
-// playlists and returns the named kind's fresh row. Replaces the
-// former per-kind refreshForYou/refreshDiscover.
+// Generic by-kind refresh. Rebuilds the caller's system playlists and
+// returns the named kind's fresh row.
export type RefreshSystemResponse = {
playlist_id: string | null;
track_count: number;
diff --git a/web/src/lib/api/queries.ts b/web/src/lib/api/queries.ts
index 09706f4a..06a49eb4 100644
--- a/web/src/lib/api/queries.ts
+++ b/web/src/lib/api/queries.ts
@@ -35,21 +35,21 @@ export const qk = {
myRequests: () => ['myRequests'] as const,
adminRequests: (status?: string) =>
['adminRequests', { status: status ?? 'all' }] as const,
- // Wanted list (M502 #5520). Wants sit under ['adminWants'] so one prefix
+ // Wanted list. Wants sit under ['adminWants'] so one prefix
// invalidation refreshes every tab and its counts.
adminWants: (state: string, q: string = '', artist: string = '') =>
['adminWants', { state, q, artist }] as const,
wantArtists: (q: string = '') => ['adminWants', 'artists', { q }] as const,
wantTracks: (id: string) => ['wantTracks', id] as const,
- // The Collection's Unmatched section (#5579).
+ // The Collection's Unmatched section.
unmatched: () => ['adminWants', 'unmatched'] as const,
adminFollows: () => ['adminFollows'] as const,
adminFollow: (artistMbid: string) => ['adminFollow', artistMbid] as const,
releaseProfiles: () => ['releaseProfiles'] as const,
- // Search and grab (M502 #5521).
+ // Search and grab.
wantCandidates: (wantId: string) => ['wantCandidates', wantId] as const,
searchSettings: () => ['searchSettings'] as const,
- // The Wanted view's Activity and Blocked tabs (#5522).
+ // The Wanted view's Activity and Blocked tabs.
downloads: () => ['adminWants', 'downloads'] as const,
releaseBlocks: () => ['adminWants', 'releaseBlocks'] as const,
myQuarantine: () => ['myQuarantine'] as const,
@@ -78,7 +78,7 @@ export const qk = {
adminDiagnosticDevices: (userId?: string) =>
['adminDiagnosticDevices', { userId: userId ?? 'all' }] as const,
smtpConfig: () => ['smtpConfig'] as const,
- // Notifications inbox (M489). Both keys sit under ['notifications'] so one
+ // Notifications inbox. Both keys sit under ['notifications'] so one
// prefix invalidation refreshes the list and the badge together.
notifications: () => ['notifications', 'list'] as const,
notificationsUnread: () => ['notifications', 'unread'] as const,
@@ -95,7 +95,7 @@ export const qk = {
['playlists', { kind: kind ?? 'user' }] as const,
playlist: (id: string) => ['playlist', id] as const,
systemPlaylistsStatus: () => ['systemPlaylistsStatus'] as const,
- // Browse indexes (#367). Keys carry no arguments — both are whole-library
+ // Browse indexes. Keys carry no arguments — both are whole-library
// indexes, and the per-genre / per-year album lists are fetched outside
// svelte-query because their selection comes from the URL.
genres: () => ['genres'] as const,
diff --git a/web/src/lib/api/requests.ts b/web/src/lib/api/requests.ts
index baeb5225..233914e0 100644
--- a/web/src/lib/api/requests.ts
+++ b/web/src/lib/api/requests.ts
@@ -3,9 +3,8 @@ import { api, apiFetch } from './client';
import { qk } from './queries';
import type { MusicRequest, RequestKind } from './types';
-/** Cadence for in-flight request polling. Tuned to match the operator's
- "fast enough to feel live, slow enough to not hammer the server"
- threshold from #369. */
+/** Cadence for in-flight request polling: fast enough to feel live, slow
+ enough not to hammer the server. */
const POLL_INTERVAL_MS = 12_000;
/** True if any row is mid-ingest (status === 'approved'). The status flips
@@ -18,7 +17,7 @@ export function hasInFlightRequest(rows: readonly MusicRequest[] | undefined): b
// CreateRequestParams is the shape callers pass; unused MBID fields are
// optional, and we omit them from the wire body rather than sending empty
// strings (the backend tolerates either, but this keeps test expectations
-// crisp and matches the spec §5 wire shape).
+// crisp and matches the server's wire shape).
export type CreateRequestParams = {
kind: RequestKind;
artist_mbid: string;
diff --git a/web/src/lib/api/suggestions.ts b/web/src/lib/api/suggestions.ts
index 2abcd30a..e782e5fe 100644
--- a/web/src/lib/api/suggestions.ts
+++ b/web/src/lib/api/suggestions.ts
@@ -11,7 +11,7 @@ export function createSuggestionsQuery(limit = 12) {
return createQuery({
queryKey: qk.suggestions(limit),
queryFn: () => listSuggestions(limit),
- staleTime: 5 * 60_000 // 5 minutes — see M5c spec §5
+ staleTime: 5 * 60_000 // 5 minutes
});
}
@@ -20,9 +20,9 @@ export function createSuggestionsQuery(limit = 12) {
// out-of-library, so there is no artists row to resolve a display name from
// and the snooze list would have nothing to render. Omitting it is a 400.
//
-// No `days` is sent. There is deliberately no duration UI yet — that knob is
-// slice 6 (#2377) — and hardcoding a value here would pin the default to the
-// client instead of the server that owns it.
+// No `days` is sent: the default is an admin setting on the server (Discover
+// tuning), and hardcoding a value here would pin it to the client instead of
+// the server that owns it.
export async function snoozeSuggestion(mbid: string, name: string): Promise {
await api.post(`/api/discover/suggestions/${encodeURIComponent(mbid)}/snooze`, { name });
}
diff --git a/web/src/lib/api/tuning.ts b/web/src/lib/api/tuning.ts
index c5c01b88..13f0840b 100644
--- a/web/src/lib/api/tuning.ts
+++ b/web/src/lib/api/tuning.ts
@@ -1,6 +1,6 @@
import { api } from './client';
-// Mirrors internal/api/admin_recommendation_tuning.go (#1250): the
+// Mirrors internal/api/admin_recommendation_tuning.go: the
// recommendation tuning lab. Field names are the server's snake_case
// keys, used verbatim in PATCH bodies.
@@ -26,9 +26,9 @@ export type TasteTuning = {
mood_scale: number;
};
-// Discover request-surface knobs (#2377). Its own scope rather than part of
+// Discover request-surface knobs. Its own scope rather than part of
// taste: snooze_days belongs here, and a snooze must never be read as taste
-// signal (#2374).
+// signal.
export type DiscoverTuning = {
tag_overlap_weight: number;
snooze_days: number;
@@ -65,7 +65,7 @@ export function resetTuning(scope: TuningScope): Promise {
return api.post(`/api/admin/recommendation-tuning/${scope}/reset`, {});
}
-// Trends (#1251): weekly per-surface outcome series with knob-turn
+// Trends: weekly per-surface outcome series with knob-turn
// markers. Mirrors internal/api/admin_recommendation_trends.go.
export type TrendPoint = {
diff --git a/web/src/lib/api/types.ts b/web/src/lib/api/types.ts
index 7e66e9a2..051371a4 100644
--- a/web/src/lib/api/types.ts
+++ b/web/src/lib/api/types.ts
@@ -41,7 +41,7 @@ export type TrackRef = {
disc_number?: number;
duration_sec: number;
stream_url: string;
- // #5418: the file is gone from the library (missing_since is set). Sent on
+ // The file is gone from the library (missing_since is set). Sent on
// direct lookups such as album detail and search; lists that pick tracks
// for playback leave missing files out instead.
unavailable?: boolean;
@@ -49,15 +49,15 @@ export type TrackRef = {
export type ArtistDetail = ArtistRef & {
albums: AlbumRef[];
- // Genres across this artist's tracks (#367). Server guarantees an array.
+ // Genres across this artist's tracks. Server guarantees an array.
genres: string[];
- // MusicBrainz id, which following is keyed by (M502 #5520); "" when none.
+ // MusicBrainz id, which following is keyed by; "" when none.
mbid: string;
};
export type AlbumDetail = AlbumRef & {
tracks: TrackRef[];
- // Genres across this album's tracks (#367). Server guarantees an array.
+ // Genres across this album's tracks. Server guarantees an array.
genres: string[];
};
@@ -93,8 +93,8 @@ export type PlaylistTrack = {
duration_sec: number;
stream_url: string | null;
added_at: string;
- // True when the track still exists but its file is missing from disk
- // (#2527). Distinct from track_id === null: that one is gone for good,
+ // True when the track still exists but its file is missing from disk.
+ // Distinct from track_id === null: that one is gone for good,
// this one keeps its play history and likes and may come back — the
// scanner un-marks it, or adopts it if it returns renamed. Both are
// unplayable; only this one is worth telling the user about.
@@ -125,9 +125,7 @@ export type EventRequest =
// `source` is the system-playlist variant the play came from
// (for_you, discover, deep_cuts, …); empty/undefined for library
// / user-playlist / radio / Subsonic. Server stores it on
- // play_events.source and uses it to advance the rotation. Drift
- // #555: the type was missing `source` so callers had no typed slot
- // for it and a "clean up extra properties" refactor could drop it.
+ // play_events.source and uses it to advance the rotation.
| { type: 'play_started'; track_id: string; client_id?: string; source?: string }
| { type: 'play_ended'; play_event_id: string; duration_played_ms: number }
| { type: 'play_skipped'; play_event_id: string; position_ms: number };
@@ -148,7 +146,7 @@ export type RequestStatus =
export type RequestKind = 'artist' | 'album' | 'track';
-// One row of the request search (GET /api/catalogue/search, M502 #5519).
+// One row of the request search (GET /api/catalogue/search).
// An album is a MusicBrainz release group; image_url is Minstrel's own art
// endpoint, which answers 404 when no source has an image.
export type CatalogueSearchResult = {
@@ -189,7 +187,7 @@ export type MusicRequest = {
requested_at: string;
updated_at: string;
// Live ingest counters. Server fills these from the matched entity's
- // children. Useful for in-flight requests so the operator can see
+ // children. Useful for in-flight requests so an admin can see
// "5 albums · 47 tracks" while the rest is still being fetched.
imported_album_count: number;
imported_track_count: number;
@@ -276,7 +274,7 @@ export type AdminPlaybackError = {
};
// deleted_album is history: an album removed whole, before Minstrel filed its
-// own music (M502).
+// own music.
export type QuarantineAction = 'resolved' | 'deleted_file' | 'deleted_album';
export type QuarantineActionRow = {
@@ -311,9 +309,9 @@ export type ArtistSuggestion = {
score: number;
attribution: SeedContribution[]; // up to 3 entries, ordered by contribution DESC
image_url?: string; // absent → card placeholder
- // Tags this candidate shares with your taste profile, strongest first (max 3,
- // #2377). Absent for most candidates — tag coverage for artists you don't own
- // is partial by nature (#2376) — so the card falls back to seed attribution.
+ // Tags this candidate shares with your taste profile, strongest first (max
+ // 3). Absent for most candidates — tag coverage for artists you don't own
+ // is partial by nature — so the card falls back to seed attribution.
matched_tags?: string[];
};
@@ -339,7 +337,7 @@ export type HomePayload = {
you_might_like_artists: ArtistRef[];
};
-// Missing files (#2527) -----------------------------------------------------
+// Missing files -----------------------------------------------------
// One track whose file the scan could not find. The text fields come from the
// tracks row rather than the filesystem — the recording is still a known thing
@@ -359,13 +357,13 @@ export type AdminMissingTrack = {
last_played_at: string | null;
};
-// What the wanted list says about this group's album (#290, M502 #5520):
+// What the wanted list says about this group's album:
// when it was wanted back, and when the library held it again. null while the
// album is inside the grace window, or when MusicBrainz can't name it.
export type AdminReacquisitionState = {
wanted_at: string | null;
held_at: string | null;
- // The want itself, as the wanted list sends it (M504), so the Missing page
+ // The want itself, as the wanted list sends it, so the Missing page
// shows its search and download and acts on it.
want: Want | null;
};
@@ -387,7 +385,7 @@ export type AdminMissingResponse = {
groups: AdminMissingGroup[];
};
-// Suspect sources (#5410) --------------------------------------------------
+// Suspect sources --------------------------------------------------
// A present track whose filename carries a video-rip marker. markers are the
// reasons, read from the file's basename: "music video", "[Audio]", "MV"...
@@ -403,12 +401,12 @@ export type AdminSuspectTrack = {
disc_number: number | null;
track_number: number | null;
markers: string[];
- // 'suspect': held back from radio and the mixes. 'fine': the operator let it
- // back. null: the resolver hasn't looked yet (M498 #5439).
+ // 'suspect': held back from radio and the mixes. 'fine': an admin let it
+ // back. null: the resolver hasn't looked yet.
verdict: SuspectVerdict | null;
verdict_at: string | null;
// The album is on the wanted list for a clean copy, and when one was last
- // searched for (#5524).
+ // searched for.
replacing: boolean;
searched_at: string | null;
};
@@ -428,10 +426,10 @@ export type AdminSuspectResponse = {
groups: AdminSuspectGroup[];
};
-// Duplicates report (#3912) -------------------------------------------------
+// Duplicates report -------------------------------------------------
// One copy in a proposed duplicate group. like_count and play_count cover every
-// user: they are what the operator weighs when choosing which copy to keep.
+// user: they are what an admin weighs when choosing which copy to keep.
export type AdminDuplicateMember = {
track_id: string;
title: string;
@@ -448,11 +446,11 @@ export type AdminDuplicateMember = {
play_count: number;
disc_number: number | null;
track_number: number | null;
- // To play each copy from the review (#6168).
+ // To play each copy from the review.
stream_url: string;
};
-// The resolver's verdict on a group (M498). null until its first pass.
+// The resolver's verdict on a group. null until its first pass.
export type DuplicateClass = 'same_release' | 'cross_release' | 'distinct' | 'mismatch' | 'review';
// exact: identical encoded audio. acoustic: the same recording, differently
@@ -465,7 +463,7 @@ export type AdminDuplicateGroup = {
survivor_track_id: string;
survivor_reason: string;
class: DuplicateClass | null;
- // Why the resolver left the group for the operator, when it had something to say.
+ // Why the resolver left the group for an admin, when it had something to say.
resolve_note: string | null;
members: AdminDuplicateMember[];
};
@@ -480,14 +478,14 @@ export type AdminDuplicateSweep = {
error_message: string | null;
};
-// What a merge did (#3911). removed_paths are the files that were deleted from
+// What a merge did. removed_paths are the files that were deleted from
// disk.
export type MergeDuplicateResult = {
survivor_track_id: string;
removed_paths: string[];
};
-// What settling one recording in several slots did (#6184). kept_track_id is
+// What settling one recording in several slots did. kept_track_id is
// null when every copy went; blocked names the releases set aside, empty when
// the album came from before Minstrel downloaded anything.
export type RefetchDuplicateResult = {
@@ -517,7 +515,7 @@ export type AdminDuplicatesResponse = {
// The report's group tabs. 'resolved' is the third tab, served separately.
export type DuplicatesView = 'review' | 'cross_release';
-// One merge the resolver made on its own (M498), from the audit log.
+// One merge the resolver made on its own, from the audit log.
// What the resolver did by itself: merged a group, dismissed two versions of a
// song as not duplicates, or removed copies that filled another song's track
// slot (their tracks stay, marked missing, and the album is fetched again).
@@ -542,15 +540,15 @@ export type ResolvedDuplicatesResponse = {
items: ResolvedDuplicateActivity[];
};
-// Wanted list (M502 #5520) ------------------------------------------------
+// Wanted list ------------------------------------------------
-// library: in the library when the list began (#5567).
+// library: in the library when the list began.
export type WantReason = 'request' | 'follow' | 'missing' | 'library';
-// all: one artist's page, every album of theirs on the list (#5572).
-// upgrade: held albums wanted for a better copy (#5524).
+// all: one artist's page, every album of theirs on the list.
+// upgrade: held albums wanted for a better copy.
export type WantState = 'wanted' | 'missing' | 'upgrade' | 'held' | 'all';
// Why a held album is wanted again: its copy is below the quality settled
-// for, or one of its tracks is a video rip (#5524).
+// for, or one of its tracks is a video rip.
export type WantUpgrade = 'quality' | 'rip';
// One release group on the wanted list. held_at is set once the library
@@ -572,10 +570,10 @@ export type Want = {
held_at: string | null;
image_url: string;
// A held album wanted for a better copy, and what the copy it has is
- // ("MP3, about 192 kbps") (#5524).
+ // ("MP3, about 192 kbps").
upgrade?: WantUpgrade;
held_quality?: string;
- // The search schedule's last look, and the download running (#5521).
+ // The search schedule's last look, and the download running.
search?: WantSearch | null;
grab?: WantGrab | null;
};
@@ -591,7 +589,7 @@ export type WantSearch = {
error: string;
};
-// What an import made of a finished download (M502 #5522).
+// What an import made of a finished download.
export type ImportState =
| ''
| 'downloading'
@@ -615,13 +613,13 @@ export type WantGrab = {
progress: number;
import_state: ImportState;
import_reason: string;
- // Why an ended download ended (#5523).
+ // Why an ended download ended.
end_detail: string;
imported_at: string | null;
imported_files: number;
};
-// Search and grab (M502 #5521) ----------------------------------------------
+// Search and grab ----------------------------------------------
export type CandidateVerdict = 'grab' | 'review' | 'refused' | 'rejected' | 'blocked';
@@ -694,26 +692,26 @@ export type SearchSettings = {
last_pass_at: string | null;
last_pass: SearchPass;
// The library folder new music is filed into ("" = the first of
- // scan_paths), and the library's folders (#5522).
+ // scan_paths), and the library's folders.
import_root: string;
scan_paths: string[];
- // The download lifecycle (#5523): an imported download seeds to this
+ // The download lifecycle: an imported download seeds to this
// ratio or for this many days (0: removed once imported; no time limit),
// and one nobody shares is given up after this many hours (0: never).
seed_ratio: number;
seed_days: number;
stall_hours: number;
- // The tier a held album is upgraded to; "" is no quality upgrades (#5524).
+ // The tier a held album is upgraded to; "" is no quality upgrades.
upgrade_until: string;
};
export type WantCounts = {
- // Artists in the Collection's catalogue (#5572).
+ // Artists in the Collection's catalogue.
artists: number;
wanted: number;
missing: number;
held: number;
- // Held albums wanted for a better copy (#5524).
+ // Held albums wanted for a better copy.
upgrades: number;
following: number;
// Downloads running, those of them waiting on a person, releases set aside.
@@ -721,11 +719,11 @@ export type WantCounts = {
attention: number;
blocked: number;
// Library albums no MusicBrainz album matched, waiting to be matched by
- // hand or left off (#5579).
+ // hand or left off.
unmatched: number;
};
-// What an import's check counted (#5522).
+// What an import's check counted.
export type ImportEvidence = {
files: number;
tracks: number;
@@ -763,7 +761,7 @@ export type Download = {
files: string[];
skipped: number;
rehomed_from?: string;
- // Why an ended download ended; for an imported one, its seeding (#5523).
+ // Why an ended download ended; for an imported one, its seeding.
end_detail: string;
seeding: boolean;
ratio: number;
@@ -772,7 +770,7 @@ export type Download = {
condition: DownloadCondition | null;
};
-// An open condition on a download, from the lifecycle's ledger (#5523).
+// An open condition on a download, from the lifecycle's ledger.
export type DownloadCondition = {
trigger: 'no_peers' | 'no_metadata' | 'client_error' | 'client_unreachable' | 'gone' | 'not_removed';
detail: string;
@@ -805,10 +803,10 @@ export type ReleaseBlock = {
export type ReleaseBlocksPage = { items: ReleaseBlock[]; limit: number; offset: number };
-// The one-time listing of the library's albums as held (#5567): when it
+// The one-time listing of the library's albums as held: when it
// finished (null while it runs), and how many albums nothing names to
// MusicBrainz, so they could not be listed.
-// One album in the Collection's Unmatched section (#5579): why no
+// One album in the Collection's Unmatched section: why no
// MusicBrainz album matched it, or that a person left it off.
export type UnmatchedAlbum = {
album_id: string;
@@ -829,7 +827,7 @@ export type LibraryListing = { listed_at: string | null; unlisted: number };
// artist heads the list when it is narrowed to one artist's page.
export type WantsPage = Page & { counts: WantCounts; library: LibraryListing; artist?: WantArtist };
-// One artist in the Collection's catalogue (#5572): how many of their albums
+// One artist in the Collection's catalogue: how many of their albums
// on the list are held, of how many.
export type WantArtist = {
artist_mbid: string;
@@ -847,7 +845,7 @@ export type WantArtist = {
export type WantArtistsPage = Page & { counts: WantCounts; library: LibraryListing };
-// An album's tracks in the Wanted view (#5572): the library's own for an
+// An album's tracks in the Wanted view: the library's own for an
// album it holds (a lost file marked missing), else MusicBrainz's standard
// edition, named by edition; none when MusicBrainz lists no edition.
export type WantTrack = { disc: number; number: number; title: string; length_ms: number; missing: boolean };
@@ -888,7 +886,7 @@ export type ReleaseProfilesResponse = {
secondary_types: string[];
};
-// What GET /api/admin/summary returns (#5605): each queue that waits on an
+// What GET /api/admin/summary returns: each queue that waits on an
// admin, with how many and since when the oldest has waited (null when empty).
export type AdminQueue = { count: number; since: string | null };
export type AdminSummary = {
@@ -900,7 +898,7 @@ export type AdminSummary = {
missing: AdminQueue;
};
-// What GET /api/admin/status returns (M504): the Overview's status sections.
+// What GET /api/admin/status returns: the Overview's status sections.
export type AdminListening = {
username: string;
track_id: string;
diff --git a/web/src/lib/api/wanted.ts b/web/src/lib/api/wanted.ts
index ee6ca812..a98200e4 100644
--- a/web/src/lib/api/wanted.ts
+++ b/web/src/lib/api/wanted.ts
@@ -21,12 +21,12 @@ import type {
WantsPage
} from './types';
-// The wanted list (M502 #5520): release groups Minstrel is to get, the
+// The wanted list: release groups Minstrel is to get, the
// artists it follows for new releases, and the release-type profiles that
// say which kinds of release a follow wants.
// q narrows the list to an artist or album whose name holds it; artist to
-// one artist's page, which the response then heads (#5572).
+// one artist's page, which the response then heads.
export async function listWants(
state: WantState,
offset: number = 0,
@@ -44,7 +44,7 @@ export function wantsNextOffset(last: WantsPage): number | undefined {
return last.items.length === 0 || loaded >= last.total ? undefined : loaded;
}
-// Loads as the page scrolls (rule 172), one tab at a time.
+// Loads as the page scrolls, one tab at a time.
export function createWantsQuery(state: WantState, q: string = '', artist: string = '') {
return createInfiniteQuery({
queryKey: qk.adminWants(state, q, artist),
@@ -57,7 +57,7 @@ export function createWantsQuery(state: WantState, q: string = '', artist: strin
});
}
-// The Collection's catalogue (#5572): every artist on the list, A–Z, with
+// The Collection's catalogue: every artist on the list, A–Z, with
// how many of their albums are held. Its first page carries every section's
// count.
export async function listWantArtists(offset: number = 0, q: string = ''): Promise {
@@ -80,7 +80,7 @@ export function createWantArtistsQuery(q: string = '') {
});
}
-// An album's tracks (#5572): the library's own for an album it holds,
+// An album's tracks: the library's own for an album it holds,
// otherwise MusicBrainz's standard edition.
export async function getWantTracks(id: string): Promise {
return api.get(`/api/admin/wanted/${id}/tracks`);
@@ -94,7 +94,7 @@ export function createWantTracksQuery(id: string) {
});
}
-// The Collection's Unmatched section (#5579): library albums no MusicBrainz
+// The Collection's Unmatched section: library albums no MusicBrainz
// album matched, and after them the ones a person left off.
export async function listUnmatched(): Promise {
const out = await api.get<{ items: UnmatchedAlbum[] }>('/api/admin/wanted/unmatched');
@@ -191,7 +191,7 @@ export async function deleteReleaseProfile(id: string): Promise {
await api.del(`/api/admin/release-profiles/${id}`);
}
-// Search and grab (M502 #5521): each want's last search, a person's Search,
+// Search and grab: each want's last search, a person's Search,
// Grab and "never this one", and the schedule's settings.
export async function listCandidates(wantId: string): Promise {
@@ -252,7 +252,7 @@ export async function putSearchSettings(body: {
return api.put('/api/admin/acquisition/settings', body);
}
-// Downloads and their imports (M502 #5522) ----------------------------------
+// Downloads and their imports ----------------------------------
// A page holding fewer than it asked for is the last.
function nextOffset(last: { items: unknown[]; limit: number; offset: number }): number | undefined {
@@ -288,7 +288,7 @@ export async function refuseDownload(id: string): Promise {
}
// A running download given up: the release is set aside for good, the
-// download removed and the album searched for again (#5523).
+// download removed and the album searched for again.
export async function abandonDownload(id: string): Promise {
await api.post(`/api/admin/downloads/${id}/abandon`, {});
}
diff --git a/web/src/lib/auth/publicRoutes.test.ts b/web/src/lib/auth/publicRoutes.test.ts
index 71e875e3..4a14ad03 100644
--- a/web/src/lib/auth/publicRoutes.test.ts
+++ b/web/src/lib/auth/publicRoutes.test.ts
@@ -6,10 +6,10 @@ describe('isPublicRoute', () => {
expect(isPublicRoute('/login')).toBe(true);
});
- // Regression guard for #376: /register was previously bounced to /login
- // by the layout guard, breaking bootstrap-admin self-registration on
- // fresh deployments. Do NOT remove /register from the public set unless
- // you have a deliberate replacement (e.g. invite-only landing page).
+ // /register must stay public: bounced to /login by the layout guard, the
+ // bootstrap admin of a fresh deployment could not register. Do NOT remove
+ // /register from the public set unless you have a deliberate replacement
+ // (e.g. invite-only landing page).
test('/register is public — bootstrap admin must reach the form', () => {
expect(isPublicRoute('/register')).toBe(true);
});
@@ -31,11 +31,9 @@ describe('isPublicRoute', () => {
expect(isPublicRoute('/loginx')).toBe(false);
});
- // Regression guard for drift #558: /forgot-password and the
- // /reset-password/ deep links must be reachable without a
- // session — the reset flow is entered by clicking an email link
- // while signed out, so the auth gate redirecting to /login broke
- // account recovery.
+ // /forgot-password and the /reset-password/ deep links must be
+ // reachable without a session — the reset flow is entered by clicking
+ // an email link while signed out.
test('/forgot-password is public', () => {
expect(isPublicRoute('/forgot-password')).toBe(true);
});
diff --git a/web/src/lib/auth/publicRoutes.ts b/web/src/lib/auth/publicRoutes.ts
index aaecf98f..b3afab4e 100644
--- a/web/src/lib/auth/publicRoutes.ts
+++ b/web/src/lib/auth/publicRoutes.ts
@@ -1,12 +1,9 @@
// Routes that the +layout.svelte auth guard must NOT redirect away from
// when the visitor is unauthenticated. Bootstrap-admin self-registration
-// (#376) requires /register to be reachable without a session — without
-// /register here, the login page's "Register" link bounces back to /login.
-//
-// Drift #558: /forgot-password and /reset-password/ were missing
-// from this set. The email-link reset flow is by definition entered
-// without a session — a signed-out user clicking their reset link was
-// being bounced to /login, breaking account recovery.
+// requires /register to be reachable without a session — without /register
+// here, the login page's "Register" link bounces back to /login. The
+// email-link reset flow (/forgot-password, /reset-password/) is by
+// definition entered without a session.
const PUBLIC_ROUTES = new Set(['/login', '/register', '/forgot-password']);
const PUBLIC_PREFIXES = ['/reset-password/'];
diff --git a/web/src/lib/auth/store.svelte.ts b/web/src/lib/auth/store.svelte.ts
index c84c2f03..777f4129 100644
--- a/web/src/lib/auth/store.svelte.ts
+++ b/web/src/lib/auth/store.svelte.ts
@@ -9,7 +9,7 @@ import { user, setUser } from './user.svelte';
export { user };
// Weekly client-driven cadence for sending the browser's current IANA
-// timezone to PUT /api/me/timezone (#392 Half B). Tracked in
+// timezone to PUT /api/me/timezone. Tracked in
// localStorage so the cadence survives tab restarts; bumped after each
// successful PUT. Failures are swallowed — server keeps its previous
// value (or 'UTC' default).
diff --git a/web/src/lib/components/AcoustIDCard.svelte b/web/src/lib/components/AcoustIDCard.svelte
index 2871725e..88fbbf1d 100644
--- a/web/src/lib/components/AcoustIDCard.svelte
+++ b/web/src/lib/components/AcoustIDCard.svelte
@@ -13,9 +13,10 @@
import SettingsGroup from '#lib/components/settings/SettingsGroup.svelte';
import SettingRow from '#lib/components/settings/SettingRow.svelte';
- // AcoustID recording-id lookup (M401): fills the MusicBrainz ids that
- // untagged tracks lack, so the similarity arms can see them. Off until the
- // operator adds their own key (rule 26), and it never gates anything else:
+ // AcoustID recording-id lookup: fills the MusicBrainz ids that untagged
+ // tracks lack, so the similarity arms can see them. Off until an admin adds
+ // their own key — a third-party account is never on by default — and it
+ // never gates anything else:
// when AcoustID is unreachable the card says so and nothing else notices.
let overview = $state(null);
diff --git a/web/src/lib/components/AcquisitionCard.svelte b/web/src/lib/components/AcquisitionCard.svelte
index 631ad512..bb1b4557 100644
--- a/web/src/lib/components/AcquisitionCard.svelte
+++ b/web/src/lib/components/AcquisitionCard.svelte
@@ -11,10 +11,10 @@
import AcquisitionServiceRow from './AcquisitionServiceRow.svelte';
import SettingsGroup from '#lib/components/settings/SettingsGroup.svelte';
- // Where Minstrel looks for music and what downloads it (M502 #5518),
- // Fabled Librarian's model: two lists of connections the operator adds and
- // configures. Nothing is seeded or copied. A FabledExchange is
- // one entry under download clients, with a switch for each of its jobs.
+ // Where Minstrel looks for music and what downloads it: two lists of
+ // connections an admin adds and configures. Nothing is seeded or copied. A
+ // FabledExchange is one entry under download clients, with a switch for each
+ // of its jobs.
let services = $state(null);
let loadError = $state(false);
@@ -125,7 +125,7 @@
{:else if services === null}
Loading indexers and download clients…
{:else}
-
+
{#if !canFetch}
No download client is switched on, so nothing can be downloaded.
diff --git a/web/src/lib/components/AcquisitionServiceRow.svelte b/web/src/lib/components/AcquisitionServiceRow.svelte
index 4c765368..48f7df91 100644
--- a/web/src/lib/components/AcquisitionServiceRow.svelte
+++ b/web/src/lib/components/AcquisitionServiceRow.svelte
@@ -13,7 +13,7 @@
import { errCode, errMessage } from '#lib/api/errors.js';
import { pushToast } from '#lib/stores/toast.svelte.js';
- // One indexer or download client (M502 #5518). The form is a copy of the
+ // One indexer or download client. The form is a copy of the
// saved row, so Save is only offered when something changed. Secrets are
// write-only: the server says whether one is set and never what it is.
diff --git a/web/src/lib/components/ActiveSessions.svelte b/web/src/lib/components/ActiveSessions.svelte
index 82ee5cd2..1b033f0f 100644
--- a/web/src/lib/components/ActiveSessions.svelte
+++ b/web/src/lib/components/ActiveSessions.svelte
@@ -120,7 +120,7 @@
}
-
+
Signed-in devices
diff --git a/web/src/lib/components/CollectionHeader.svelte b/web/src/lib/components/CollectionHeader.svelte
index 3c6c20c5..a347bba6 100644
--- a/web/src/lib/components/CollectionHeader.svelte
+++ b/web/src/lib/components/CollectionHeader.svelte
@@ -1,6 +1,6 @@
@@ -37,7 +37,7 @@
} = $props();
// Art comes from Minstrel's catalogue endpoint, which answers 404 when no
- // source has an image (M502 #5519): show the icon then, not a broken image.
+ // source has an image: show the icon then, not a broken image.
// Reset when the card is reused for another result.
let imageFailed = $state(false);
$effect(() => {
@@ -118,7 +118,7 @@
Icon-only to keep Request unambiguously the primary action, with
the intent carried by the accessible name. "Not right now" is the
whole point of the wording: this parks a suggestion, it does not
- record an opinion about the artist (rule #101).
+ record an opinion about the artist.
-->