fix(aiui): content loads progressively — own first, peers when they arrive

Regression from wiring the peers scope: requestArchyAllContent awaited all
three scopes together, so the grid waited on the slowest. `peers` browses every
federated node over FIPS (Tor fallback) and routinely takes tens of seconds or
times out when a peer is offline. On-device that read as
"content(peers) failed: Content request timed out" plus an AIUI that felt very
slow to open — with nothing rendered meanwhile, even though local content was
ready immediately.

Now `own` paints as soon as it lands and `owned`/`peers` fold in as they
arrive. A scope that times out costs only its own results.

Also records the operator's console findings as tasks: the `files` context
timeout, the web-search CSP block (13-09, now firing on every query), the
strfry icon 404, IndeeHub's relay.nostr.band socket, and the ask that `!archy`
over mesh be able to action container commands with text responses.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
archipelago
2026-08-07 03:08:03 -04:00
co-authored by Claude Opus 5
parent 086d381c4f
commit 05b459a65f
2 changed files with 85 additions and 38 deletions
+56 -38
View File
@@ -270,55 +270,73 @@ export function useArchy() {
*/
async function requestArchyAllContent() {
if (!isInitialized.value) return
const scopes: Array<'own' | 'owned' | 'peers'> = ['own', 'owned', 'peers']
const results = await Promise.all(
scopes.map((s) =>
archyBridge
.requestArchyContent('all', s)
.then((res) => {
// Log the denial per scope. A silent null here is indistinguishable
// from "the node genuinely has no content", which is exactly the
// ambiguity that made an ungranted Media/File permission look like
// a broken films search on-device.
if (!res.permitted) {
console.warn(
`[AIUI Archy] content(${s}): not permitted — enable Media/File access in Archy Settings`,
)
return null
}
return res
})
.catch((err) => {
console.warn(`[AIUI Archy] content(${s}) failed:`, (err as Error)?.message ?? err)
return null
}),
),
)
const seen = new Set<string>()
// PROGRESSIVE, not all-at-once. The first version awaited all three scopes
// together, so the grid waited on the slowest — and `peers` browses every
// federated node over FIPS (falling back to Tor), which routinely takes
// tens of seconds or times out entirely when a peer is offline. On-device
// that showed up as "content(peers) failed: Content request timed out" and
// an AIUI that felt very slow to open, with NOTHING rendered in the
// meantime even though local content was ready immediately.
//
// So: paint `own` as soon as it lands, then fold in the slower sources as
// they arrive. A scope that times out costs only its own results.
const films: Film[] = []
const podcasts: Podcast[] = []
for (const res of results) {
if (!res) continue
const seen = new Set<string>()
const merge = (res: { films?: unknown; podcasts?: unknown } | null) => {
if (!res) return false
let added = false
for (const f of (res.films ?? []) as Film[]) {
const key = `film:${f.id}`
if (seen.has(key)) continue
seen.add(key)
films.push(f)
const k = `film:${f.id}`
if (seen.has(k)) continue
seen.add(k); films.push(f); added = true
}
for (const p of (res.podcasts ?? []) as Podcast[]) {
const key = `pod:${p.id}`
if (seen.has(key)) continue
seen.add(key)
podcasts.push(p)
const k = `pod:${p.id}`
if (seen.has(k)) continue
seen.add(k); podcasts.push(p); added = true
}
return added
}
const panel = useContentPanel()
panel.setArchyContent({ films, songs: panel.panelSongs.value, podcasts })
const paint = () => {
const panel = useContentPanel()
panel.setArchyContent({ films: [...films], songs: panel.panelSongs.value, podcasts: [...podcasts] })
}
const fetchScope = (scope: 'own' | 'owned' | 'peers') =>
archyBridge
.requestArchyContent('all', scope)
.then((res) => {
if (!res.permitted) {
console.warn(`[AIUI Archy] content(${scope}): not permitted — enable Media/File access in Archy Settings`)
return null
}
return res
})
.catch((err) => {
// A slow or absent peer is ordinary, not exceptional.
console.warn(`[AIUI Archy] content(${scope}) failed:`, (err as Error)?.message ?? err)
return null
})
// Local content first — it is fast and is what the operator sees instantly.
if (merge(await fetchScope('own'))) paint()
// Then the slower sources, each painting as it lands rather than blocking
// the others or the initial render.
await Promise.all(
(['owned', 'peers'] as const).map((scope) =>
fetchScope(scope).then((res) => {
if (merge(res)) paint()
}),
),
)
}
/**
* Request the node's real music library (13-11 — the D-13 wave) and hand
* it to the same `setArchyContent` sink `requestArchyContent` uses, so