From 11b9cb501200fa60c57eaa85ed8c9dfcd3b997ae Mon Sep 17 00:00:00 2001 From: archipelago Date: Thu, 6 Aug 2026 17:10:04 -0400 Subject: [PATCH] =?UTF-8?q?fix(13-11):=20a=20films=20search=20found=20noth?= =?UTF-8?q?ing=20=E2=80=94=20owned=20and=20peer=20content=20had=20no=20cal?= =?UTF-8?q?ler?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reported on-device: searching for films in AIUI returns nothing. Init only ever asked for scope 'own' (content.list-mine — this node's own shared files), so IndeeHub and everything else purchased, which lives in 'owned' (content.owned-list), and other nodes' catalogs in 'peers' were never fetched. Both scopes existed only as type-signature options with no call site anywhere in the app. requestArchyAllContent() now loads all three concurrently and merges once. Merged rather than three setArchyContent calls because that sink REPLACES films/podcasts — separate pushes would leave only whichever resolved last, the same class of bug as the shared sequence guard fixed in aac81503. Deduped by id, since a title can legitimately appear both owned locally and offered by a peer. Each scope is caught individually so one dead or slow peer costs only its own results, which is normal rather than exceptional. requestArchyContent also stops clobbering songs with an empty array, mirroring what requestArchyLibrary already did for films/podcasts. vue-tsc clean, 3/3 useArchy tests pass, and the change is verified present in the built bundle rather than assumed. Co-Authored-By: Claude Opus 5 (1M context) --- aiui/packages/app/src/composables/useArchy.ts | 65 ++++++++++++++++++- 1 file changed, 62 insertions(+), 3 deletions(-) diff --git a/aiui/packages/app/src/composables/useArchy.ts b/aiui/packages/app/src/composables/useArchy.ts index e7a0523a..22da47dd 100644 --- a/aiui/packages/app/src/composables/useArchy.ts +++ b/aiui/packages/app/src/composables/useArchy.ts @@ -140,7 +140,7 @@ export function useArchy() { // functions already resolve to a silent, logged no-op when the user // hasn't granted Media/File access (`permitted: false`), so this never // throws into `init()`. - void requestArchyContent('all', 'own') + void requestArchyAllContent() void requestArchyLibrary('own') } @@ -239,9 +239,10 @@ export function useArchy() { console.warn('[AIUI Archy] content: not permitted — user should enable Media/File access in Archy Settings') return } - useContentPanel().setArchyContent({ + const panel = useContentPanel() + panel.setArchyContent({ films: res.films as Film[], - songs: res.songs as Song[], + songs: panel.panelSongs.value, podcasts: res.podcasts as Podcast[], }) } catch (err) { @@ -249,6 +250,63 @@ export function useArchy() { } } + /** + * Load every content scope the node can offer, merged into one grid. + * + * `own` alone was all init ever asked for, which meant a films search showed + * nothing but this node's own shared files: **IndeeHub and anything else + * purchased live in `owned` (`content.owned-list`), and other nodes' catalogs + * in `peers` — and neither scope had a single caller anywhere in the app.** + * They existed only as type-signature options. + * + * Scopes are fetched concurrently and merged once, rather than each calling + * `setArchyContent` itself: that sink REPLACES films/podcasts, so three + * separate pushes would leave only whichever resolved last. Deduped by id + * because the same title can legitimately appear in more than one scope + * (owned locally and offered by a peer). + * + * A failing scope must not cost the others — a dead or slow peer is normal, + * not exceptional — so each is caught individually and contributes nothing. + */ + 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) => (res.permitted ? res : null)) + .catch((err) => { + console.warn(`[AIUI Archy] content(${s}) failed:`, (err as Error)?.message ?? err) + return null + }), + ), + ) + + const seen = new Set() + const films: Film[] = [] + const podcasts: Podcast[] = [] + for (const res of results) { + if (!res) continue + for (const f of (res.films ?? []) as Film[]) { + const key = `film:${f.id}` + if (seen.has(key)) continue + seen.add(key) + films.push(f) + } + 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 panel = useContentPanel() + panel.setArchyContent({ films, songs: panel.panelSongs.value, podcasts }) + } + /** * Request the node's real music library (13-11 — the D-13 wave) and hand * it to the same `setArchyContent` sink `requestArchyContent` uses, so @@ -381,6 +439,7 @@ export function useArchy() { readFile, tailLogs, requestArchyContent, + requestArchyAllContent, requestArchyLibrary, buildArchyContext, }