Merge pull request #225 from cuckium/main

Use live Workers that actively fetch APIs in favour of static instances.json
This commit is contained in:
Eduard Prigoana 2026-02-21 16:59:52 +02:00 committed by GitHub
commit b05166efa5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 314 additions and 178 deletions

View file

@ -105,7 +105,7 @@ monochrome/
├── 📁 public/ # Static assets
│ ├── assets/ # Images, icons, fonts
│ ├── manifest.json # PWA manifest
│ └── instances.json # API instances configuration
│ └── instances.json # API instances configuration (deprecated)
├── 📄 index.html # Application entry point
├── 📄 vite.config.js # Build and PWA configuration
├── 📄 package.json # Dependencies and scripts

View file

@ -31,7 +31,12 @@ These instances provide the tidal-ui web interface, not monochrome:
## API Instances
Monochrome uses the Hi-Fi API under the hood. These are available API endpoints that can be used with Monochrome or other Hi-Fi based applications:
Monochrome uses the Hi-Fi API under the hood. Live, up-to-date status trackers (which return JSON) can be found below:
- https://tidal-uptime.jiffy-puffs-1j.workers.dev/
- https://tidal-uptime.props-76styles.workers.dev/
These are available API endpoints that can be used with Monochrome or other Hi-Fi based applications:
### Official & Community APIs

View file

@ -2,27 +2,52 @@
class ServerAPI {
constructor() {
this.INSTANCES_URL = 'https://raw.githubusercontent.com/Monochrome-music/monochrome/main/instances.json';
this.INSTANCES_URLS = [
'https://tidal-uptime.jiffy-puffs-1j.workers.dev/',
'https://tidal-uptime.props-76styles.workers.dev/',
];
this.apiInstances = null;
}
async getInstances() {
if (this.apiInstances) return this.apiInstances;
try {
const response = await fetch(this.INSTANCES_URL);
if (!response.ok) throw new Error('Failed to fetch instances');
const data = await response.json();
this.apiInstances = data.api || [];
return this.apiInstances;
} catch (error) {
console.error('Failed to load instances:', error);
return [
'https://triton.squid.wtf',
'https://wolf.qqdl.site',
'https://tidal-api.binimum.org',
'https://monochrome-api.samidy.com',
];
let data = null;
const urls = [...this.INSTANCES_URLS].sort(() => Math.random() - 0.5);
for (const url of urls) {
try {
const response = await fetch(url);
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
data = await response.json();
break;
} catch (error) {
console.warn(`Failed to fetch from ${url}:`, error);
}
}
if (data) {
this.apiInstances = (data.api || [])
.map((item) => item.url || item)
.filter((url) => !url.includes('spotisaver.net'));
return this.apiInstances;
}
console.error('Failed to load instances from all uptime APIs');
return [
'https://eu-central.monochrome.tf',
'https://us-west.monochrome.tf',
'https://arran.monochrome.tf',
'https://triton.squid.wtf',
'https://api.monochrome.tf',
'https://monochrome-api.samidy.com',
'https://maus.qqdl.site',
'https://vogel.qqdl.site',
'https://katze.qqdl.site',
'https://hund.qqdl.site',
'https://tidal.kinoplus.online',
'https://wolf.qqdl.site',
];
}
async fetchWithRetry(relativePath) {

View file

@ -2,27 +2,52 @@
class ServerAPI {
constructor() {
this.INSTANCES_URL = 'https://raw.githubusercontent.com/Monochrome-music/monochrome/main/public/instances.json';
this.INSTANCES_URLS = [
'https://tidal-uptime.jiffy-puffs-1j.workers.dev/',
'https://tidal-uptime.props-76styles.workers.dev/',
];
this.apiInstances = null;
}
async getInstances() {
if (this.apiInstances) return this.apiInstances;
try {
const response = await fetch(this.INSTANCES_URL);
if (!response.ok) throw new Error('Failed to fetch instances');
const data = await response.json();
this.apiInstances = data.api || [];
return this.apiInstances;
} catch (error) {
console.error('Failed to load instances:', error);
return [
'https://triton.squid.wtf',
'https://wolf.qqdl.site',
'https://tidal-api.binimum.org',
'https://monochrome-api.samidy.com',
];
let data = null;
const urls = [...this.INSTANCES_URLS].sort(() => Math.random() - 0.5);
for (const url of urls) {
try {
const response = await fetch(url);
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
data = await response.json();
break;
} catch (error) {
console.warn(`Failed to fetch from ${url}:`, error);
}
}
if (data) {
this.apiInstances = (data.api || [])
.map((item) => item.url || item)
.filter((url) => !url.includes('spotisaver.net'));
return this.apiInstances;
}
console.error('Failed to load instances from all uptime APIs');
return [
'https://eu-central.monochrome.tf',
'https://us-west.monochrome.tf',
'https://arran.monochrome.tf',
'https://triton.squid.wtf',
'https://api.monochrome.tf',
'https://monochrome-api.samidy.com',
'https://maus.qqdl.site',
'https://vogel.qqdl.site',
'https://katze.qqdl.site',
'https://hund.qqdl.site',
'https://tidal.kinoplus.online',
'https://wolf.qqdl.site',
];
}
async fetchWithRetry(relativePath) {

View file

@ -2,27 +2,52 @@
class ServerAPI {
constructor() {
this.INSTANCES_URL = 'https://raw.githubusercontent.com/Monochrome-music/monochrome/main/public/instances.json';
this.INSTANCES_URLS = [
'https://tidal-uptime.jiffy-puffs-1j.workers.dev/',
'https://tidal-uptime.props-76styles.workers.dev/',
];
this.apiInstances = null;
}
async getInstances() {
if (this.apiInstances) return this.apiInstances;
try {
const response = await fetch(this.INSTANCES_URL);
if (!response.ok) throw new Error('Failed to fetch instances');
const data = await response.json();
this.apiInstances = data.api || [];
return this.apiInstances;
} catch (error) {
console.error('Failed to load instances:', error);
return [
'https://triton.squid.wtf',
'https://wolf.qqdl.site',
'https://tidal-api.binimum.org',
'https://monochrome-api.samidy.com',
];
let data = null;
const urls = [...this.INSTANCES_URLS].sort(() => Math.random() - 0.5);
for (const url of urls) {
try {
const response = await fetch(url);
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
data = await response.json();
break;
} catch (error) {
console.warn(`Failed to fetch from ${url}:`, error);
}
}
if (data) {
this.apiInstances = (data.api || [])
.map((item) => item.url || item)
.filter((url) => !url.includes('spotisaver.net'));
return this.apiInstances;
}
console.error('Failed to load instances from all uptime APIs');
return [
'https://eu-central.monochrome.tf',
'https://us-west.monochrome.tf',
'https://arran.monochrome.tf',
'https://triton.squid.wtf',
'https://api.monochrome.tf',
'https://monochrome-api.samidy.com',
'https://maus.qqdl.site',
'https://vogel.qqdl.site',
'https://katze.qqdl.site',
'https://hund.qqdl.site',
'https://tidal.kinoplus.online',
'https://wolf.qqdl.site',
];
}
async fetchWithRetry(relativePath) {

View file

@ -14,27 +14,52 @@ function getTrackArtists(track = {}, { fallback = 'Unknown Artist' } = {}) {
class ServerAPI {
constructor() {
this.INSTANCES_URL = 'https://raw.githubusercontent.com/Monochrome-music/monochrome/main/public/instances.json';
this.INSTANCES_URLS = [
'https://tidal-uptime.jiffy-puffs-1j.workers.dev/',
'https://tidal-uptime.props-76styles.workers.dev/',
];
this.apiInstances = null;
}
async getInstances() {
if (this.apiInstances) return this.apiInstances;
try {
const response = await fetch(this.INSTANCES_URL);
if (!response.ok) throw new Error('Failed to fetch instances');
const data = await response.json();
this.apiInstances = data.api || [];
return this.apiInstances;
} catch (error) {
console.error('Failed to load instances from GitHub:', error);
return [
'https://triton.squid.wtf',
'https://wolf.qqdl.site',
'https://tidal-api.binimum.org',
'https://monochrome-api.samidy.com',
];
let data = null;
const urls = [...this.INSTANCES_URLS].sort(() => Math.random() - 0.5);
for (const url of urls) {
try {
const response = await fetch(url);
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
data = await response.json();
break;
} catch (error) {
console.warn(`Failed to fetch from ${url}:`, error);
}
}
if (data) {
this.apiInstances = (data.api || [])
.map((item) => item.url || item)
.filter((url) => !url.includes('spotisaver.net'));
return this.apiInstances;
}
console.error('Failed to load instances from all uptime APIs');
return [
'https://eu-central.monochrome.tf',
'https://us-west.monochrome.tf',
'https://arran.monochrome.tf',
'https://triton.squid.wtf',
'https://api.monochrome.tf',
'https://monochrome-api.samidy.com',
'https://maus.qqdl.site',
'https://vogel.qqdl.site',
'https://katze.qqdl.site',
'https://hund.qqdl.site',
'https://tidal.kinoplus.online',
'https://wolf.qqdl.site',
];
}
async fetchWithRetry(relativePath) {

View file

@ -106,19 +106,7 @@ export function trackToggleMute(muted) {
trackEvent('Toggle Mute', { muted });
}
export function trackSeek(position, duration) {
const progress = duration ? Math.round((position / duration) * 100) : 0;
// Track seek at 25%, 50%, 75% milestones
if (progress >= 25 && progress < 30) {
trackEvent('Seek', { milestone: '25%', position });
} else if (progress >= 50 && progress < 55) {
trackEvent('Seek', { milestone: '50%', position });
} else if (progress >= 75 && progress < 80) {
trackEvent('Seek', { milestone: '75%', position });
}
}
// Track listening progress milestones (10%, 25%, 50%, 75%, 90%, 100%)
// Track listening progress milestones (10%, 50%, 90%, 100%)
export function trackListeningProgress(track, percent) {
trackEvent('Listening Progress', {
track_id: track?.id || 'unknown',

View file

@ -42,17 +42,28 @@ export class LosslessAPI {
async fetchWithRetry(relativePath, options = {}) {
const type = options.type || 'api';
const instances = await this.settings.getInstances(type);
let instances = await this.settings.getInstances(type);
if (instances.length === 0) {
throw new Error(`No API instances configured for type: ${type}`);
}
if (options.minVersion) {
instances = instances.filter((instance) => {
if (!instance.version) return false;
return parseFloat(instance.version) >= parseFloat(options.minVersion);
});
if (instances.length === 0) {
throw new Error(`No API instances configured for type: ${type} with minVersion: ${options.minVersion}`);
}
}
const maxTotalAttempts = instances.length * 2; // Allow some retries across instances
let lastError = null;
let instanceIndex = Math.floor(Math.random() * instances.length);
for (let attempt = 1; attempt <= maxTotalAttempts; attempt++) {
const baseUrl = instances[instanceIndex % instances.length];
const instance = instances[instanceIndex % instances.length];
const baseUrl = typeof instance === 'string' ? instance : instance.url;
const url = baseUrl.endsWith('/') ? `${baseUrl}${relativePath.substring(1)}` : `${baseUrl}${relativePath}`;
try {
@ -644,7 +655,7 @@ export class LosslessAPI {
const cached = await this.cache.get('mix', id);
if (cached) return cached;
const response = await this.fetchWithRetry(`/mix/?id=${id}`, { type: 'api' });
const response = await this.fetchWithRetry(`/mix/?id=${id}`, { type: 'api', minVersion: '2.3' });
const data = await response.json();
const mixData = data.mix;
@ -778,7 +789,10 @@ export class LosslessAPI {
if (cached) return cached;
try {
const response = await this.fetchWithRetry(`/artist/similar/?id=${artistId}`, { type: 'api' });
const response = await this.fetchWithRetry(`/artist/similar/?id=${artistId}`, {
type: 'api',
minVersion: '2.3',
});
const data = await response.json();
// Handle various response structures
@ -829,7 +843,10 @@ export class LosslessAPI {
if (cached) return cached;
try {
const response = await this.fetchWithRetry(`/album/similar/?id=${albumId}`, { type: 'api' });
const response = await this.fetchWithRetry(`/album/similar/?id=${albumId}`, {
type: 'api',
minVersion: '2.3',
});
const data = await response.json();
const items = data.items || data.albums || data.data || (Array.isArray(data) ? data : []);
@ -976,7 +993,10 @@ export class LosslessAPI {
if (cached) return cached;
try {
const response = await this.fetchWithRetry(`/recommendations/?id=${id}`, { type: 'api' });
const response = await this.fetchWithRetry(`/recommendations/?id=${id}`, {
type: 'api',
minVersion: '2.4',
});
const json = await response.json();
const data = json.data || json;

View file

@ -27,7 +27,6 @@ import {
trackSkipTrack,
trackToggleShuffle,
trackToggleRepeat,
trackSeek,
trackAddToQueue,
trackPlayNext,
trackLikeTrack,
@ -137,9 +136,6 @@ export function initializePlayerEvents(player, audioPlayer, scrobbler, ui) {
progressFill.style.width = `${(currentTime / duration) * 100}%`;
currentTimeEl.textContent = formatTime(currentTime);
// Track seek milestones
trackSeek(currentTime, duration);
// Log to history after 10 seconds of playback
if (currentTime >= 10 && player.currentTrack && player.currentTrack.id !== historyLoggedTrackId) {
historyLoggedTrackId = player.currentTrack.id;
@ -1668,7 +1664,6 @@ export function initializeTrackInteractions(player, api, mainContent, contextMen
api.getTrackRecommendations(clickedTrack.id).then((recs) => {
if (recs && recs.length > 0) {
player.addToQueue(recs);
showNotification(`Added ${recs.length} recommendations to queue`);
}
});
}

View file

@ -1,120 +1,133 @@
//storage.js
export const apiSettings = {
STORAGE_KEY: 'monochrome-api-instances-v8',
INSTANCES_URL: 'instances.json',
STORAGE_KEY: 'monochrome-api-instances-v9',
INSTANCES_URLS: [
'https://tidal-uptime.jiffy-puffs-1j.workers.dev/',
'https://tidal-uptime.props-76styles.workers.dev/',
],
defaultInstances: { api: [], streaming: [] },
instancesLoaded: false,
_loadPromise: null,
async loadInstancesFromGitHub() {
if (this.instancesLoaded) {
return this.defaultInstances;
}
try {
const response = await fetch(this.INSTANCES_URL);
if (!response.ok) throw new Error('Failed to fetch instances');
if (this._loadPromise) {
return this._loadPromise;
}
const data = await response.json();
this._loadPromise = (async () => {
const cachedData = localStorage.getItem(this.STORAGE_KEY);
if (cachedData) {
try {
const parsed = JSON.parse(cachedData);
const now = Date.now();
// Check if cached data is less than 15 minutes old
if (parsed.timestamp && now - parsed.timestamp < 15 * 60 * 1000) {
this.defaultInstances = parsed.data;
this.instancesLoaded = true;
this._loadPromise = null;
return this.defaultInstances;
}
} catch (e) {
console.warn('Failed to parse cached instances:', e);
}
}
let data = null;
let fetchError = null;
// Shuffle URLs to pick a random one first
const urls = [...this.INSTANCES_URLS].sort(() => Math.random() - 0.5);
for (const url of urls) {
try {
const response = await fetch(url);
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
data = await response.json();
break; // Success, exit loop
} catch (error) {
console.warn(`Failed to fetch from ${url}:`, error);
fetchError = error;
}
}
if (!data) {
console.error('Failed to load instances from all uptime APIs:', fetchError);
this.defaultInstances = {
api: [
{ url: 'https://eu-central.monochrome.tf', version: '2.4' },
{ url: 'https://us-west.monochrome.tf', version: '2.4' },
{ url: 'https://arran.monochrome.tf', version: '2.4' },
{ url: 'https://triton.squid.wtf', version: '2.4' },
{ url: 'https://api.monochrome.tf', version: '2.3' },
{ url: 'https://monochrome-api.samidy.com', version: '2.3' },
{ url: 'https://maus.qqdl.site', version: '2.2' },
{ url: 'https://vogel.qqdl.site', version: '2.2' },
{ url: 'https://katze.qqdl.site', version: '2.2' },
{ url: 'https://hund.qqdl.site', version: '2.2' },
{ url: 'https://tidal.kinoplus.online', version: '2.2' },
{ url: 'https://wolf.qqdl.site', version: '2.2' },
],
streaming: [
{ url: 'https://arran.monochrome.tf', version: '2.4' },
{ url: 'https://triton.squid.wtf', version: '2.4' },
{ url: 'https://api.monochrome.tf', version: '2.3' },
{ url: 'https://monochrome-api.samidy.com', version: '2.3' },
{ url: 'https://maus.qqdl.site', version: '2.2' },
{ url: 'https://vogel.qqdl.site', version: '2.2' },
{ url: 'https://katze.qqdl.site', version: '2.2' },
{ url: 'https://hund.qqdl.site', version: '2.2' },
{ url: 'https://wolf.qqdl.site', version: '2.2' },
],
};
this.instancesLoaded = true;
this._loadPromise = null;
return this.defaultInstances;
}
let groupedInstances = { api: [], streaming: [] };
if (Array.isArray(data)) {
// Legacy array format
groupedInstances.api = [...data];
groupedInstances.streaming = [...data];
} else {
// New object format or legacy object format
if (data.api && Array.isArray(data.api)) {
const isSimpleArray = data.api.length > 0 && typeof data.api[0] === 'string';
if (isSimpleArray) {
groupedInstances.api = [...data.api];
} else {
for (const [, config] of Object.entries(data.api)) {
if (config.cors === false && Array.isArray(config.urls)) {
groupedInstances.api.push(...config.urls);
}
}
}
}
if (data.api && Array.isArray(data.api)) {
groupedInstances.api = data.api.filter((instance) => !instance.url.includes('spotisaver.net'));
}
if (data.streaming && Array.isArray(data.streaming)) {
groupedInstances.streaming = [...data.streaming];
} else if (groupedInstances.api.length > 0) {
groupedInstances.streaming = [...groupedInstances.api];
}
if (data.streaming && Array.isArray(data.streaming)) {
groupedInstances.streaming = data.streaming.filter(
(instance) => !instance.url.includes('spotisaver.net')
);
} else if (groupedInstances.api.length > 0) {
groupedInstances.streaming = [...groupedInstances.api];
}
this.defaultInstances = groupedInstances;
this.instancesLoaded = true;
try {
localStorage.setItem(
this.STORAGE_KEY,
JSON.stringify({
timestamp: Date.now(),
data: groupedInstances,
})
);
} catch (e) {
console.warn('Failed to cache instances:', e);
}
this._loadPromise = null;
return groupedInstances;
} catch (error) {
console.error('Failed to load instances from GitHub:', error);
this.defaultInstances = {
api: [
'https://eu-central.monochrome.tf',
'https://us-west.monochrome.tf',
'https://arran.monochrome.tf',
'https://api.monochrome.tf',
'https://triton.squid.wtf',
'https://wolf.qqdl.site',
'https://monochrome-api.samidy.com',
'https://maus.qqdl.site',
'https://tidal.kinoplus.online',
'https://hund.qqdl.site',
'https://vogel.qqdl.site',
],
streaming: [
'https://arran.monochrome.tf',
'https://triton.squid.wtf',
'https://wolf.qqdl.site',
'https://maus.qqdl.site',
'https://vogel.qqdl.site',
'https://katze.qqdl.site',
'https://hund.qqdl.site',
],
};
this.instancesLoaded = true;
return this.defaultInstances;
}
})();
return this._loadPromise;
},
async getInstances(type = 'api', _sortBySpeed = false) {
let instancesObj;
const stored = localStorage.getItem(this.STORAGE_KEY);
if (stored) {
instancesObj = JSON.parse(stored);
// love it when local storage doesnt update
if (instancesObj?.api?.length === 2) {
const hasBinimum = instancesObj.api.some((url) => {
try {
const urlObj = new URL(url);
return urlObj.hostname === 'tidal-api.binimum.org';
} catch {
return false;
}
});
const hasSamidy = instancesObj.api.some((url) => {
try {
const urlObj = new URL(url);
return urlObj.hostname === 'monochrome-api.samidy.com';
} catch {
return false;
}
});
if (hasBinimum && hasSamidy) {
localStorage.removeItem(this.STORAGE_KEY);
instancesObj = null;
}
}
}
if (!instancesObj) {
instancesObj = await this.loadInstancesFromGitHub();
}
instancesObj = await this.loadInstancesFromGitHub();
const targetUrls = instancesObj[type] || instancesObj.api || [];
if (targetUrls.length === 0) return [];

15
package-lock.json generated
View file

@ -81,6 +81,7 @@
"integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/code-frame": "^7.27.1",
"@babel/generator": "^7.28.5",
@ -1609,6 +1610,7 @@
"integrity": "sha512-FA5LmZVF1VziNc0bIdCSA1IoSVnDCqE8HJIZZv2/W8YmoAM50+tnUgJR/gQZwEeIMleuIOnRnHA/UaZRNeV4iQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@keyv/serialize": "^1.1.1"
}
@ -1650,6 +1652,7 @@
}
],
"license": "MIT",
"peer": true,
"engines": {
"node": ">=18"
},
@ -1693,6 +1696,7 @@
}
],
"license": "MIT",
"peer": true,
"engines": {
"node": ">=18"
}
@ -3270,6 +3274,7 @@
"resolved": "https://registry.npmjs.org/@svta/cml-xml/-/cml-xml-1.0.1.tgz",
"integrity": "sha512-11LkJa5kDEcsRMWkVI1ABH3KLCxGoiSVe4kQ293ItVj8ncTTQ7htmCGiJDjS+Cmy35UgF3e/vc0ysJIiWRTx2g==",
"license": "Apache-2.0",
"peer": true,
"engines": {
"node": ">=20"
},
@ -3318,6 +3323,7 @@
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"acorn": "bin/acorn"
},
@ -3341,6 +3347,7 @@
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.3",
"fast-uri": "^3.0.1",
@ -3638,6 +3645,7 @@
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"baseline-browser-mapping": "^2.9.0",
"caniuse-lite": "^1.0.30001759",
@ -4675,6 +4683,7 @@
"integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.1",
@ -7296,6 +7305,7 @@
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"nanoid": "^3.3.11",
"picocolors": "^1.1.1",
@ -7379,6 +7389,7 @@
"integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"cssesc": "^3.0.0",
"util-deprecate": "^1.0.2"
@ -8484,6 +8495,7 @@
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"@csstools/css-parser-algorithms": "^3.0.5",
"@csstools/css-syntax-patches-for-csstree": "^1.0.19",
@ -8934,6 +8946,7 @@
"integrity": "sha512-t/R3R/n0MSwnnazuPpPNVO60LX0SKL45pyl9YlvxIdkH0Of7D5qM2EVe+yASRIlY5pZ73nclYJfNANGWPwFDZw==",
"dev": true,
"license": "BSD-2-Clause",
"peer": true,
"dependencies": {
"@jridgewell/source-map": "^0.3.3",
"acorn": "^8.15.0",
@ -9308,6 +9321,7 @@
"integrity": "sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"esbuild": "^0.27.0",
"fdir": "^6.5.0",
@ -9744,6 +9758,7 @@
"integrity": "sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"rollup": "dist/bin/rollup"
},

View file

@ -62,7 +62,7 @@ export default defineConfig(({ mode }) => {
},
],
},
includeAssets: ['instances.json', 'discord.html'],
includeAssets: ['discord.html'],
manifest: false, // Use existing public/manifest.json
}),
],