fix(anilist): paginate large libraries to avoid CPU budget kills (#184)

* fix(anilist): paginate large libraries to avoid CPU budget kills

Chunk GraphQL fetches and defer merge, sort, and cover work to update()
ticks so 1000+ anime and manga entries load without blocking callbacks.

* fix(anilist): handle OAuth callback immediately and simplify service helpers

Process browser login tokens synchronously from runStream and file poll, keep a 500ms update interval during OAuth, and avoid duplicate OAuth result emissions from Python.
This commit is contained in:
Cleboost
2026-07-31 17:12:24 -04:00
committed by GitHub
parent dffd46ab4d
commit be208b42c5
7 changed files with 682 additions and 198 deletions
+24 -7
View File
@@ -15,6 +15,7 @@ local visibleRowLimit = INITIAL_VISIBLE_ROWS
local snapshot = noctalia.state.get("anilist_snapshot") or {
revision = 0,
oauthLoading = false,
loading = false,
refreshing = false,
busy = false,
@@ -22,6 +23,7 @@ local snapshot = noctalia.state.get("anilist_snapshot") or {
viewer = nil,
anime = {},
manga = {},
loadProgress = nil,
}
local mediaTab = "ANIME"
@@ -229,6 +231,17 @@ local function settingsButton()
})
end
local function loadProgressText()
local progress = snapshot.loadProgress
if type(progress) ~= "table" then
return tr("loading")
end
return tr("loading_progress", {
anime = progress.animeLoaded or 0,
manga = progress.mangaLoaded or 0,
})
end
local function renderLogin()
local children = {
ui.row({ align = "center", justify = "space_between", gap = 8 }, {
@@ -239,8 +252,10 @@ local function renderLogin()
ui.label({ text = tr("login_help"), fontSize = 12, color = "on_surface_variant", maxLines = 8 }),
}
if snapshot.loading then
if snapshot.oauthLoading then
table.insert(children, ui.label({ text = tr("login_waiting"), color = "primary", fontSize = 13 }))
elseif snapshot.loading then
table.insert(children, ui.label({ text = loadProgressText(), color = "primary", fontSize = 13 }))
else
table.insert(children, ui.button({
text = tr("connect"),
@@ -578,17 +593,17 @@ local function renderLibrary()
}),
}))
if snapshot.loading and not snapshot.viewer then
table.insert(children, ui.label({ text = tr("loading"), color = "on_surface_variant", padding = { top = 12 } }))
if snapshot.loading then
table.insert(children, ui.label({ text = loadProgressText(), color = "on_surface_variant", padding = { top = 12 } }))
elseif snapshot.error ~= "" then
table.insert(children, ui.label({ text = tr("error", { message = snapshot.error }), color = "error", padding = { top = 12 } }))
elseif snapshot.refreshing then
elseif snapshot.refreshing and #rows == 0 then
table.insert(children, ui.label({ text = tr("refreshing"), color = "on_surface_variant", padding = { top = 8 } }))
elseif snapshot.busy then
table.insert(children, ui.label({ text = tr("updating"), color = "on_surface_variant", padding = { top = 8 } }))
end
if (not snapshot.loading or snapshot.viewer) and #rows == 0 then
if not snapshot.loading and #rows == 0 then
table.insert(children, ui.label({ text = tr("empty"), color = "on_surface_variant", padding = { top = 12 } }))
else
local listChildren = {}
@@ -647,7 +662,7 @@ function onOpen(_context)
noctalia.state.set("anilist_open", true)
resetListView()
if snapshot.viewer and snapshot.viewer.id then
sendCommand("refresh", { mediaType = mediaTab })
sendCommand("refresh", { mediaType = mediaTab, silent = true })
end
dirty = true
render()
@@ -656,7 +671,9 @@ end
function onClose()
coverPreview = nil
noctalia.state.set("anilist_open", false)
noctalia.setUpdateInterval(LIST_IDLE_INTERVAL_MS)
if not snapshot.oauthLoading then
noctalia.setUpdateInterval(LIST_IDLE_INTERVAL_MS)
end
end
function update()