@@ -4061,9 +4063,9 @@ export class UIRenderer {
const quote = decodeHtml(review.text || review.quote || 'No review text available.');
reviewdiv.innerHTML = `
-
diff --git a/js/utils.js b/js/utils.js
index 25bceba..256367a 100644
--- a/js/utils.js
+++ b/js/utils.js
@@ -3,7 +3,7 @@ import { modernSettings } from './ModernSettings.js';
import { SVG_ATMOS } from './icons.js';
import { qualityBadgeSettings, coverArtSizeSettings, trackDateSettings } from './storage.js';
-export const QUALITY = 'HI_RES_LOSSLESS';
+export const QUALITY = 'LOSSLESS';
export const REPEAT_MODE = {
OFF: 0,
@@ -339,6 +339,8 @@ export const deriveTrackQuality = (track) => {
const candidates = [
deriveQualityFromTags(track.mediaMetadata?.tags),
deriveQualityFromTags(track.album?.mediaMetadata?.tags),
+ deriveQualityFromTags(track.mediaTags),
+ deriveQualityFromTags(track.album?.mediaTags),
normalizeQualityToken(track.audioQuality),
];
diff --git a/styles.css b/styles.css
index 99711b8..1bd604e 100644
--- a/styles.css
+++ b/styles.css
@@ -1043,6 +1043,44 @@ ul {
padding-bottom: 160px !important;
}
+.server-disruption-sidebar {
+ display: flex;
+ align-items: flex-start;
+ gap: 0.375rem;
+ padding: 0.4rem 0.6rem;
+ margin-bottom: 1rem;
+ background: rgba(245, 158, 11, 0.12);
+ border: 1px solid rgba(245, 158, 11, 0.25);
+ border-radius: var(--radius-md);
+ color: #f59e0b;
+ font-size: 0.7rem;
+ line-height: 1.3;
+}
+
+.server-disruption-sidebar .disruption-icon {
+ font-size: 0.8rem;
+ flex-shrink: 0;
+ margin-top: 1px;
+}
+
+.server-disruption-sidebar .disruption-dismiss {
+ margin-left: auto;
+ background: none;
+ border: none;
+ color: #f59e0b;
+ font-size: 1rem;
+ cursor: pointer;
+ padding: 0 0.125rem;
+ line-height: 1;
+ opacity: 0.7;
+ transition: opacity 0.15s;
+ flex-shrink: 0;
+}
+
+.server-disruption-sidebar .disruption-dismiss:hover {
+ opacity: 1;
+}
+
#page-background {
position: absolute;
top: 0;
@@ -2221,6 +2259,18 @@ input[type='search']::-webkit-search-cancel-button {
position: relative;
}
+.track-item.no-duration {
+ grid-template-columns: 40px 1fr auto;
+}
+
+.track-item.no-duration .track-item-duration {
+ display: none;
+}
+
+.track-item.no-duration.track-item--inline-like {
+ grid-template-columns: 40px 1fr auto auto;
+}
+
.track-item:hover {
background-color: var(--secondary);
transform: scale(1.005);
@@ -4879,6 +4929,14 @@ input:checked + .slider::before {
padding: var(--spacing-sm) var(--spacing-md);
}
+.skeleton-track.no-duration {
+ grid-template-columns: 40px 1fr 40px;
+}
+
+.skeleton-track.no-duration .skeleton-track-duration {
+ display: none;
+}
+
.skeleton-track-number {
width: 24px;
height: 20px;
diff --git a/test-search.js b/test-search.js
index f44e1ac..39c67c4 100644
--- a/test-search.js
+++ b/test-search.js
@@ -1,8 +1,27 @@
import { HiFiClient } from './js/HiFi.ts';
+import { LosslessAPI } from './js/api.js';
+
+// mock out modules to make LosslessAPI load in bun
+import { mock } from 'bun:test';
+mock.module('./js/icons.ts', () => ({}));
+mock.module('./js/settings.js', () => ({ devModeSettings: { isEnabled: () => false }, syncManager: {}, musicProviderSettings: {}, audioSettings: {}, apiSettings: {} }));
+
+globalThis.localStorage = { getItem: () => null, setItem: () => {}, removeItem: () => {} };
+globalThis.window = { matchMedia: () => ({ matches: false }) };
+
async function test() {
- const client = new HiFiClient();
- const res = await client.query('/search/?q=alskdjfalksjdfld&limit=5');
- const json = await res.json();
- console.log(JSON.stringify(json.data || {}));
+ await HiFiClient.initialize();
+ const api = new LosslessAPI({ getInstances: () => [] });
+
+ // mock cache
+ api.cache = { get: () => null, set: () => {} };
+
+ api.fetchWithRetry = async function(relativePath, options) {
+ console.log("fetchWithRetry called:", relativePath);
+ return HiFiClient.instance.query(relativePath);
+ };
+
+ const res = await api.search('coldplay');
+ console.log("Returned tracks:", res.tracks?.items?.length);
}
-void test();
+test().catch(console.error);