diff --git a/apps/frontend/src/components/ui/moderation/checklist/ModerationChecklist.vue b/apps/frontend/src/components/ui/moderation/checklist/ModerationChecklist.vue index 83f45c88e6..25d7a4d8be 100644 --- a/apps/frontend/src/components/ui/moderation/checklist/ModerationChecklist.vue +++ b/apps/frontend/src/components/ui/moderation/checklist/ModerationChecklist.vue @@ -777,11 +777,17 @@ function isEligibleQueueCandidate(result: QueueCandidateCheck | undefined): bool return !result.locked || !!result.expired || !!result.isOwnLock } +function isNotFoundError(error: unknown): boolean { + if (!error || typeof error !== 'object') return false + const fetchError = error as { statusCode?: number; response?: { status?: number } } + return fetchError.statusCode === 404 || fetchError.response?.status === 404 +} + function notifySkippedQueueProjects(count: number) { if (count <= 0) return addNotification({ title: 'Skipped projects', - text: `Skipped ${count} project(s) already moderated or locked by others.`, + text: `Skipped ${count} project(s) already moderated, deleted, or locked by others.`, type: 'info', autoCloseMs: 2000, }) @@ -812,10 +818,10 @@ async function batchCheckQueueCandidates( projectIds.map(async (id) => { const [lockResponse, projectData] = await Promise.all([ moderationQueue.checkLock(id), - useBaseFetch(`project/${id}`, { method: 'GET' }).catch(() => null), + useBaseFetch(`project/${id}`, { method: 'GET' }), ]) - const status = (projectData as { status?: string } | null)?.status + const status = (projectData as { status?: string }).status return { id, @@ -825,7 +831,7 @@ async function batchCheckQueueCandidates( slug: (projectData as { slug?: string } | null)?.slug, projectType: (projectData as { project_type?: string } | null)?.project_type, status, - isProcessing: projectData === null ? true : status === 'processing', + isProcessing: status === 'processing', } }), ) @@ -833,6 +839,8 @@ async function batchCheckQueueCandidates( checks.forEach((result, index) => { if (result.status === 'fulfilled') { results.set(result.value.id, result.value) + } else if (isNotFoundError(result.reason)) { + results.set(projectIds[index], { locked: false, isProcessing: false }) } else { results.set(projectIds[index], { locked: false, isProcessing: true }) } @@ -979,7 +987,7 @@ async function skipToNextProject() { debug('[skipToNextProject] No eligible projects in queue') addNotification({ title: 'No projects available', - text: 'All remaining projects are already moderated or locked by others.', + text: 'All remaining projects are already moderated, deleted, or locked by others.', type: 'warning', }) } @@ -1649,9 +1657,15 @@ async function sendMessage(status: ProjectStatus) { } } - await refreshModerationCaches(threadId) - const willHaveNext = await moderationQueue.completeCurrentProject(projectId, 'completed') + // Set both states together - hasNextProject MUST be set before done + // to avoid the race condition where done=true renders with hasNextProject=false + hasNextProject.value = willHaveNext + done.value = true + clearGeneratedMessageState() + await nextTick() + + await refreshModerationCaches(threadId) await Promise.race([ moderationQueue.releaseLock(projectId), @@ -1661,16 +1675,9 @@ async function sendMessage(status: ProjectStatus) { if (projectFixChanges?.slug) { const urlType = getProjectTypeForUrlShorthand(projectV2.value.project_type, [], tags.value) localStorage.setItem('moderation-checklist-finished', projectId) - clearGeneratedMessageState() await navigateTo(`/${urlType}/${projectFixChanges.slug}/moderation`, { replace: true }) return } - - // Set both states together - hasNextProject MUST be set before done - // to avoid the race condition where done=true renders with hasNextProject=false - hasNextProject.value = willHaveNext - done.value = true - clearGeneratedMessageState() } catch (error) { console.error('Error submitting moderation:', error) addNotification({ @@ -1733,7 +1740,7 @@ async function endChecklist(status?: string) { ) addNotification({ title: 'No projects available', - text: 'All remaining projects are already moderated or locked by others.', + text: 'All remaining projects are already moderated, deleted, or locked by others.', type: 'warning', }) }