Matches what Homebrew 7's style check wants; Ruby flight blocks are
only tolerated in third-party taps for now.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
$GITHUB_SERVER_URL is the runner's own address for Forgejo
(http://localhost:3000 on mainframe), so the cask pointed its url and
homepage at localhost.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Forgejo's SSH server listens on 2200. Port 22 is the host's own sshd,
which only admits root, so the clone was refused.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
desktop/rust-toolchain.toml pins 1.98.0, but Setup Rust installs the
targets into stable, so the x86_64 Tauri build found no x86_64 std.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Forgejo ignores max-parallel, so the two matrix jobs still ran at once on
the single Mac runner and collided on the act cache's git locks. Build
aarch64 then x86_64 within one job and upload them as one artifact.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Both matrix jobs land on the same Mac, whose runners share the act
action cache; running them concurrently collides on its git shallow.lock.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
actions/setup-go downloads Go on the host-mode Mac runner but then fails
to find it ("/bin/sh: version: command not found"). Fetch the latest
patch of go.mod's release from go.dev instead; keep setup-go for Windows.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Forgejo runner resolves bare action names against code.forgejo.org,
which doesn't mirror dtolnay/rust-toolchain (or rust-cache, setup-msys2),
so the macOS build jobs failed before starting.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Without a go.mod, Dagger regenerated one on every run and resolved
otel to the latest release (v1.47.0), while the Go SDK pins otel/log
to v0.16.0. The newer otel/internal/global imports otel/log, which
imports it back, so the module failed to build with an import cycle.
Pin otel, otel/trace and otel/metric to v1.43.0 (matching otel-go and
otel/sdk) and commit go.mod/go.sum so CI stops re-resolving.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CI now talks to the shared engine on mainframe instead of starting its
own; keep the CLI in step with it and with dagger.json.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 0a1c52794c)
- Pin checkout/setup-node/setup-go to their Node 20 majors; the Linux
runner on mainframe can't run node24 actions yet.
- The Homebrew tap moved with the Quine → quine-global org rename.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
GitHub's actions/upload-artifact@v7 and download-artifact@v8 only work
against github.com/GHES; Forgejo needs its own v4-protocol forks.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Change the bundle identifier to com.quineglobal.drome (Tauri config,
Flatpak manifest/metainfo, Linux build, Justfile, READMEs). On first
launch the app moves the old com.quintodrome.desktop data folder over so
existing users keep their library.
- Ad-hoc sign macOS builds (signingIdentity "-") so the whole bundle is
signed; the linker-only signature made Gatekeeper report the app as
"damaged".
- Forgejo desktop workflow: cross-compile the Intel build on the Mac runner,
drop Windows (no runner), create releases on the pushed tag instead of a
"v"-less copy, fail on upload errors, and update the Quine/homebrew-quintodrome
cask after each release.
- Add Flatpak AppStream metainfo and a 512px icon, and document the macOS
"damaged" workaround in the README and GitHub release notes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
layout() bailed out silently whenever window.inner_size() or
scale_factor() failed or reported 0x0 - which can happen during the
setup() call on Linux/Wayland before the compositor has realized the
window. When that happened, the toolbar webview was left in its
default auto-resize state (filling the whole window) while the
content webview kept its hardcoded 800x600 initial size from
add_child(), producing an oversized toolbar and a small, disconnected
content pane.
Now the toolbar is forced out of auto-resize with an explicit size at
creation time regardless of whether layout() succeeds, the content
webview's initial size is derived from the real window size (falling
back to the configured 1280x800) instead of an arbitrary guess, and a
one-shot re-layout runs ~250ms after startup as a safety net for cases
where no later Resized event corrects an initially-wrong layout.
layout() also logs why it bails now instead of failing silently.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
v1.0.0-alpha is the first tag, so the changelog had no previous tag
to diff against and pulled in the full inherited history (~4800
commits), blowing past GitHub's release body size limit. Disable
the changelog for now, and fix the footer's hardcoded
navidrome/navidrome compare link to point at the actual repo and
only render once a previous tag exists.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The package globs assumed 0.x versions (navidrome_0*), so a 1.x tag
produced an empty package list and upload-packages failed with an
empty matrix.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The jwx library always deserializes numeric claims from a parsed token as
float64, so the int and int64 branches in getIntClaim could never succeed
and were dead code. Keep only the float64 path, which is the one actually
exercised by the token round-trip, and update the comment to document the
library behavior.
* fix(smartplaylist): support isMissing/isPresent operators on ReplayGain fields
ReplayGain values are stored in dedicated nullable columns (rg_album_gain,
rg_album_peak, rg_track_gain, rg_track_peak) rather than in the media_file.tags
JSON blob. The isMissing/isPresent operators previously only supported tag and
role fields, causing two failure modes:
1. Using the documented alias names (replaygain_album_gain etc.) from PR #5256:
these got registered as JSON tags from mappings.yaml, so isMissing queried
json_tree(media_file.tags, '$.replaygain_album_gain') which is always empty
-> the playlist matched ALL songs.
2. Using the canonical field names (rgalbumgain etc.): not a tag/role, so SQL
generation returned an error. Because refreshSmartPlaylist deletes old tracks
before regenerating, the abort left the playlist empty.
Fix: add Nullable bool to FieldInfo and mark the four ReplayGain fields. Add
static alias entries (replaygain_album_gain -> rgalbumgain etc.) with
Numeric+Nullable set; because AddTagNames skips names already in the field map,
these static entries take precedence over the mappings.yaml tag registration.
missingExpr now emits IS NULL / IS NOT NULL for nullable column fields instead
of the json_tree lookup.
Fixes#5584
* chore(smartplaylist): address code review feedback
- Simplify the isMissing/isPresent unsupported-field error message,
removing the internal "nullable fields" jargon
- Standardize comments on mappings.yaml (the actual filename)
- Clarify the alias precedence comment in the LookupField test
* fix: load ND_DEFAULTLANGUAGE on app startup
Added in to apply on initial mount, ensuring the locale is set even when the login page is skipped by reverse-proxy authentication. Removed the redundant language-init effect from . Fixes#3605.
* style(ui): format App.jsx with Prettier
Ran Prettier on ui/src/App.jsx to satisfy code style checks after adding default-language useEffect.
* fix(ui): move default language initialization to Admin component
Signed-off-by: Deluan <deluan@navidrome.org>
* fix(ui): streamline locale setting in App component
Signed-off-by: Deluan <deluan@navidrome.org>
---------
Signed-off-by: Deluan <deluan@navidrome.org>
Add Catppuccin Latte (the light version) theme based on the existing Catppuccin Macchiato theme.
The palette and player styling are adapted for light mode while staying as close as practical
to the existing Macchiato theme behavior. I've opted to use gray for the
color for controls.
The dark version appears to mix a few control/accent colors,
so for Latte I standardized those choices. This might be worth looking
into in a separate PR. It uses gray and blue.
Signed-off-by: Love Billenius <lovebillenius@disroot.org>
Co-authored-by: Deluan Quintão <deluan@navidrome.org>
Signed-off-by: Deluan <deluan@navidrome.org>
* Add Moonbase theme
A warm dark theme with gold (#d4a039) accents on deep charcoal
backgrounds (#0a0a09/#141413). Features muted cream text (#e5ddd3),
copper error states (#c45c3c), and subtle earthy secondary tones.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix review comments on Moonbase theme
- Fix CSS selector: use :not(.player-delete) instead of :not([class=".player-delete"])
- Fix MuiFormHelperText override structure: target error key directly
- Remove empty icon: {} and avatar: {} from NDLogin overrides
- Use comma-separated rgba syntax and hex for linear-gradient
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Add Moonbase Alpha (light) and rename dark to Moonbase Bravo
Split the Moonbase theme into a complementary pair:
- Moonbase Alpha: warm cream/stone light theme with deep gold accents
- Moonbase Bravo: the original deep charcoal dark theme
Both share the same gold (#d4a039) brand accent, copper error states,
and earthy neutral palette. Alpha uses darkened gold (#9a7420) for
better contrast on light backgrounds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Deluan Quintão <deluan@navidrome.org>
* fix(ui): update Spanish translations and add missing gain keys
- Add missing 'albumGain' and 'trackGain' keys (matches recent additions in pt-br, ru)
- Translate 'Playlists' and 'Shared Playlists' to 'Listas de reproducción' / '...compartidas'
- Translate 'OFFLINE' (server down indicator) to 'DESCONECTADO'
Spanish translation now covers 553/553 keys (was 551/553).
Signed-off-by: Daniel Banariba <banaribad@gmail.com>
* fix(ui): address review feedback on Spanish translations
- Use 'Ganancia del álbum' (with article 'del') for consistency with the existing pattern in line 624 ('album': 'Ganancia del álbum') and 'Artista del álbum'. Thanks @gemini-code-assist for the catch.
- Revert 'playlists' and 'sharedPlaylists' to keep the loanword 'Playlist(s)' which is the form actually used by Spanish-speaking music app users (Spotify ES, etc.) and matches existing usage elsewhere in this same file (e.g. line 48 'Agregar a la playlist').
Signed-off-by: Daniel Banariba <banaribad@gmail.com>
---------
Signed-off-by: Daniel Banariba <banaribad@gmail.com>
Co-authored-by: Deluan Quintão <deluan@navidrome.org>
* test: fix flaky tests in utils/cache
Two tests in the utils/cache suite were timing- and ordering-dependent
and failed intermittently on CI (notably on the Windows runner).
The FileHaunter tests raced the asynchronous cache-cleanup goroutine with
a fixed 400ms sleep, then asserted the directory state once. On slow
runners the haunter had not finished scrubbing, so the assertion saw the
original files and failed. Replace the fixed sleep with Eventually polling
so the assertions wait for the haunter to converge. While doing so, the
exact set and count of reaped files proved nondeterministic (the empty
file is double-counted in the size loop and LRU survivors depend on
OS access-time ordering), so the assertions now check the haunter's
actual guarantees: the empty file is always scrubbed and the cache stays
within the configured maxSize/maxItems bound. This also lets the
previously-disabled maxItems context and its commented-out assertions be
re-enabled.
The HTTPClient 'caches repeated requests' test relied on a shared
requestsReceived counter that was never reset in BeforeEach. Under
randomized spec order another spec could run first and leave the counter
non-zero, breaking the first assertion. Reset the counter and header in
BeforeEach to make the spec independent of execution order.
Verified with: ginkgo -race -repeat=80 --randomize-all ./utils/cache/
* test: surface errors in dirSize and align Eventually with house style
Address code review feedback on the cache flaky-test fix:
- dirSize now returns (uint64, error) and the maxSize spec asserts the
error is nil. Previously a ReadDir/Info failure silently returned 0,
which always satisfies '<= maxSize' and would mask a real filesystem
error as a passing test.
- dirSize skips non-regular entries (info.Mode().IsRegular()) to match
its doc comment and avoid counting directories or symlinks.
- The Eventually blocks now use .WithTimeout()/.WithPolling() with
time.Duration values instead of string-literal durations, matching the
prevailing pattern in the test suite.
Share repository read methods (Get, GetAll, Read, ReadAll, Exists, Count,
CountAll) did not apply an owner filter, so non-admin users saw shares
belonging to other users. The write paths already enforced per-user ownership;
this brings reads in line with them.
Add an addRestriction()/ownerFilter() based scope to share reads, keeping
admins and the headless public-share resolution path unrestricted. Route share
and player Delete through a new base-repo deleteOwned() primitive that applies
the ownership predicate in the DELETE's WHERE clause (atomic, no select-then-
delete window) and classifies a zero-row result as permission-denied vs
not-found, mirroring updateOwned. The addRestriction helper and the write-miss
classifier are hoisted onto the base repository so player and share share one
implementation.
Also map rest.ErrPermissionDenied and rest.ErrNotFound in the Subsonic error
handler so ownership/not-found failures from the rest-backed repositories
return the proper Subsonic codes (50 / 70) instead of a generic error.
Covered by unit tests (persistence, subsonic error mapping) and an end-to-end
cross-user sharing isolation test.
The native API exposes a `path` query param on /api/song, but it was not
registered in the media file filter map. Unmapped real columns fall through
to a default LIKE predicate that emits an unqualified `path LIKE ?`. Since
the song query joins the library table (which also has a `path` column),
SQLite returned "ambiguous column name: path" and the request failed with
HTTP 500.
Register a dedicated path filter qualified to media_file.path, resolving the
ambiguity. The value is matched with startsWith semantics (LIKE arg || '%')
against the library-relative path stored in media_file.path.
To register it inline (without a one-off wrapper), startsWithFilter now takes
a bound field and returns a filterFunc, mirroring containsFilter. The two
existing callers are updated accordingly, and the now-unused withTableName
helper is removed. The user 'name' filter, which previously relied on
withTableName, is now qualified directly as user.name; tests are added to
guard that filter against the same column-ambiguity class (the user query
also joins the library table, which has a name column).
Signed-off-by: Deluan <deluan@navidrome.org>