feat(13-11): wire requestArchyLibrary + fire content/library fetch from a live init-time event (GAP-FOUND)

AIUI asks for the library the same way it asks for content, and the
fetch now actually fires without anyone typing a magic phrase:

- useArchy.ts: requestArchyLibrary(scope) sibling of requestArchyContent
  (13-06), same bridge call with kind: 'library', routed through the
  existing setArchyContent so the songs bucket fills exactly the way
  films already does.
- init() now calls both requestArchyContent('all','own') and
  requestArchyLibrary('own') once, fire-and-forget, immediately after
  archyBridge.init() — the GAP-FOUND fix. 13-06 built the whole
  content:request/content:push machinery and unit-tested it end to end,
  but nothing in the live UI ever called it (13-06-SUMMARY.md's Known
  Limitations); the fetch is now triggered by a real init-time UI event,
  not merely callable.
- useContentPanel.ts's setArchyContent now also opens the panel and
  populates availableTabs/activeTab/panelTitle when Archy supplied
  non-empty content — previously only the data refs were set while the
  tab bar and panelOpen stayed whatever the last regex-driven chat turn
  left them, so real content could sit fully populated and still never
  render. An empty bundle never force-opens the panel.

Deviation (Rule 2, mirrors 13-06's own archyBridge.ts precedent): kind:
'library' genuinely needs a different node-side RPC (music.list-tracks,
real tag-extracted metadata) than content.* (ContentItem has no artist/
album/duration field at all) — contextBroker.ts's handleContentRequest
gained one branch (fetchLibraryContent) to route it, and
aiui-protocol.ts's AIUIContentRequest.kind union gained the 'library'
literal, and archyBridge.ts's requestArchyContent kind param widened to
match. No second channel, no new message type, no new listener — the
existing content:request/content:push channel and its kind discriminator
carry this exactly as 13-06 designed it to. Full detail in the SUMMARY.

neode-ui: 926/926 tests green, vue-tsc -b clean. aiui: 341/344 (3
pre-existing, documented failures unrelated to this plan — 13-06/13-10
already recorded them), vue-tsc --noEmit clean.
This commit is contained in:
archipelago
2026-08-05 18:29:17 -04:00
parent abe77ebe7e
commit d25aea125f
8 changed files with 288 additions and 3 deletions
@@ -297,6 +297,31 @@ export function useContentPanel() {
panelSongs.value = bundle.songs ?? []
panelPodcasts.value = bundle.podcasts ?? []
archyContentActive.value = true
// 13-11 (GAP-FOUND 2026-08-03): `availableTabs`/`activeTab`/`panelOpen`
// were previously untouched here — only `updatePanelFromText`'s regex
// path ever set them, so real Archy-sourced content could sit fully
// populated in these three refs while the tab bar and grid stayed
// whatever the last (or no) chat turn left them: closed, or showing
// only 'prompt' (13-06's own Known Limitations, this plan's must_haves
// GAP-FOUND). Only touches these three refs when Archy actually
// supplied non-empty content — an empty/never-granted library must not
// force the panel open on every mount.
const archyTabs: ContentTab[] = []
if (panelFilms.value.length > 0) archyTabs.push('film')
if (panelSongs.value.length > 0) archyTabs.push('song')
if (panelPodcasts.value.length > 0) archyTabs.push('podcast')
if (archyTabs.length > 0) {
availableTabs.value = [...archyTabs, 'prompt']
if (!archyTabs.includes(activeTab.value)) activeTab.value = archyTabs[0]!
if (panelFilms.value.length === 1) panelTitle.value = panelFilms.value[0]!.title
else if (panelFilms.value.length > 1) panelTitle.value = `${panelFilms.value.length} Films`
else if (panelSongs.value.length === 1) panelTitle.value = panelSongs.value[0]!.title
else if (panelSongs.value.length > 1) panelTitle.value = `${panelSongs.value.length} Songs`
else if (panelPodcasts.value.length === 1) panelTitle.value = panelPodcasts.value[0]!.title
else if (panelPodcasts.value.length > 1) panelTitle.value = `${panelPodcasts.value.length} Podcasts`
panelOpen.value = true
}
}
function setActiveTab(tab: ContentTab) {