feat(ia): wave 3 — Subscriptions landing answers 'what needs me, what came in?'
Daily-use reorder of the Subscriptions tab: needs-attention strip first (FailingSourcesCard moves up from below the Downloads fold — a broken subscription was invisible unless you went looking), then a new Recent arrivals card (real downloads only, no-change scans filtered out, artist links), then the source list. Both cards render nothing when there's nothing to say. Retry logic moves into the downloads store (retrySource / retryAllFailing) so the needs-attention card and the Downloads maintenance menu share one implementation — single-retry forces past cooldown, bulk keeps cooldown enforcement, same tally shape. The card's Logs button deep-links into the Downloads tab pre-filtered (?source_id now watched, not just read on mount). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CDgx8bQS5YrGRK76v8HUnM
This commit is contained in:
@@ -3,6 +3,7 @@ import { ref } from 'vue'
|
||||
import { useApi } from '../composables/useApi.js'
|
||||
import { useAsyncAction } from '../composables/useAsyncAction.js'
|
||||
import { useInflightToken } from '../composables/useInflightToken.js'
|
||||
import { useSourcesStore } from './sources.js'
|
||||
|
||||
export const useDownloadsStore = defineStore('downloads', () => {
|
||||
const api = useApi()
|
||||
@@ -106,6 +107,44 @@ export const useDownloadsStore = defineStore('downloads', () => {
|
||||
return failing.value
|
||||
}
|
||||
|
||||
// --- Failing-source retries (shared by the Subscriptions needs-attention
|
||||
// card and the Downloads maintenance menu — one implementation for both
|
||||
// surfaces). Data-only: callers own the toasts, per this store's style.
|
||||
// A single deliberate retry forces past cooldown; BULK retries keep
|
||||
// cooldown enforcement ON so N failing sources on one platform don't all
|
||||
// retry into the very rate limit the cooldown is preventing.
|
||||
async function retrySource(source) {
|
||||
const sourcesStore = useSourcesStore()
|
||||
try {
|
||||
await sourcesStore.checkNow(source.id, { force: true })
|
||||
return 'queued'
|
||||
} catch (e) {
|
||||
if (e?.body?.download_event_id) return 'already_running'
|
||||
throw e
|
||||
} finally {
|
||||
await Promise.all([loadFailing(), loadStats(24)])
|
||||
}
|
||||
}
|
||||
|
||||
async function retryAllFailing(sources) {
|
||||
const sourcesStore = useSourcesStore()
|
||||
const tally = { ok: 0, deferred: 0, conflict: 0 }
|
||||
try {
|
||||
for (const s of sources) {
|
||||
try {
|
||||
const body = await sourcesStore.checkNow(s.id)
|
||||
if (body?.status === 'deferred') tally.deferred += 1
|
||||
else tally.ok += 1
|
||||
} catch (e) {
|
||||
if (e?.body?.download_event_id) tally.conflict += 1
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
await Promise.all([loadFailing(), loadStats(24)])
|
||||
}
|
||||
return tally
|
||||
}
|
||||
|
||||
async function loadActive() {
|
||||
const [running, pending] = await Promise.all([
|
||||
api.get('/api/downloads', { params: { status: 'running', limit: 50 } }),
|
||||
@@ -128,5 +167,6 @@ export const useDownloadsStore = defineStore('downloads', () => {
|
||||
loadFirst, loadMore, loadOne, loadLastForSource, applyFilter,
|
||||
closeDetail, loadStats,
|
||||
loadActivity, loadFailing, loadActive, recoverStalled,
|
||||
retrySource, retryAllFailing,
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user