fix: resolve UI state reset and backlog sync hang

- Fix client startup order: fetch/apply server UI state BEFORE loadGuilds() to prevent overwriting persisted state with default guild
- Remove auto-post of first guild in loadGuilds() — let server state drive selection
- Refactor collectWatchableChannels() to collect text channels fast first, then discover threads in parallel with 5s per-channel timeout and 30s overall timeout to prevent blocking message sync
- Ignore /favicon.ico 404 in error logging to reduce noise

Fixes:
- UI state now persists across restart (was being overwritten by client startup race)
- Backlog sync no longer hangs on thread discovery (was blocking before message sync)
- Cleaner logs without favicon 404 errors

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
MythEclipse
2026-05-14 03:45:51 +07:00
parent d4a4f737a8
commit 0a5cedfed1
3 changed files with 42 additions and 14 deletions

View File

@@ -17,24 +17,51 @@ function isWatchableChannel(channel: { type?: string; messages?: unknown }): boo
async function collectWatchableChannels(guild: any): Promise<any[]> {
const channels: any[] = [];
// Fast pass: collect text channels from cache only
for (const channel of guild.channels.cache.values()) {
if (isWatchableChannel(channel)) {
channels.push(channel);
}
if (channel.threads?.fetch) {
for (const archived of [false, true]) {
const fetched = await channel.threads
.fetch({ archived, limit: 100 })
.catch(() => null);
if (!fetched?.threads) continue;
for (const thread of fetched.threads.values()) {
if (isWatchableChannel(thread)) channels.push(thread);
}
}
}
}
// Slow pass: discover threads with timeout per channel (non-blocking to message sync)
const threadPromises: Promise<void>[] = [];
for (const channel of guild.channels.cache.values()) {
if (!channel.threads?.fetch) continue;
threadPromises.push(
(async () => {
for (const archived of [false, true]) {
try {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 5000);
const fetched = await Promise.race([
channel.threads.fetch({ archived, limit: 100 }),
new Promise((_, reject) => controller.signal.addEventListener('abort', () => reject(new Error('timeout')))),
]).catch(() => null);
clearTimeout(timeout);
if (!fetched?.threads) continue;
for (const thread of fetched.threads.values()) {
if (isWatchableChannel(thread)) channels.push(thread);
}
} catch {
// Skip this channel's threads on timeout/error
}
}
})()
);
}
// Wait for all thread discoveries with overall timeout
await Promise.race([
Promise.all(threadPromises),
new Promise((resolve) => setTimeout(resolve, 30000)),
]).catch(() => {
logger.warn("Thread discovery timeout, proceeding with cached channels");
});
return Array.from(new Map(channels.map((channel) => [channel.id, channel])).values());
}