Files

20 KiB

phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
phase plan type wave depends_on files_modified autonomous requirements must_haves
13-aiui-functional-conversational-node-control-and-content-surf 11 execute 4
13-07
13-06
neode-ui/src/composables/archyContentAdapter.ts
neode-ui/src/composables/__tests__/archyContentAdapter.test.ts
neode-ui/src/components/cloud/ShareModal.vue
/home/archipelago/Projects/AIUI/packages/app/src/composables/useArchy.ts
true
AIUI-03
truths artifacts key_links
AIUI's SongGrid shows the node's real music library — albums, artists and tracks from the index, not a MIME-filtered folder listing (D-13, D-12)
An .m4a, .aac, .opus or .wma file shared from the cloud view gets a real audio MIME type, routes to the global bottom-bar player, and auto-files to Music instead of Documents
Audio opens in the global bottom-bar player and never in the lightbox — the rule enforced across five existing call sites is not broken by the new path
The music track reached the UI without any control-track or content-track plan depending on a music plan, and without the phase-closing gate 13-15 depending on one either (D-13)
path provides contains
neode-ui/src/composables/archyContentAdapter.ts music.* records mapped onto AIUI's Song/Album shape, alongside the existing ContentItem mapping adaptLibraryTracks
from to via pattern
neode-ui/src/composables/archyContentAdapter.ts core/archipelago/src/api/rpc/music.rs music.list-albums / music.list-tracks feed the songs bucket of the existing content:push channel music.list-
from to via pattern
neode-ui/src/components/cloud/ShareModal.vue neode-ui/src/composables/useAudioPlayer.ts a correct audio MIME on m4a/aac/opus/wma is what routes the file to the bottom-bar player audio/
Light up `SongGrid` from the real library, and fix the share-path bug that would otherwise make the library's most common formats second-class everywhere else.

D-13 is explicit that the music library "lands as its own wave of plans inside Phase 13, not blocking the rest — peer files, movies and conversational control ship on their own track and the library lights up SongGrid when ready." This is that plan. Its depends_on points at the music indexer and the content adapter; no plan on the control or content track depends on any music plan, and neither does the phase-closing gate 13-15, so the track independence D-13 requires holds literally in the wave graph rather than only in prose.

That makes this a terminal plan: nothing lists it in depends_on, by design. It is not orphaned — it owns AIUI-03's SongGrid and share-MIME deliverables, it lands at wave 4 well ahead of the wave-8 gate, and 13-15 step 7b reads its summary as a best-effort input and records the result. But if it slips, is red, or is deferred, 13-15 records that and the phase closes on the control and content tracks anyway. That is the whole point of D-13.

The second half is a verified, directly-relevant landmine: neode-ui/src/components/cloud/ShareModal.vue line 358 maps mp3, flac, ogg and wav and omits m4a, aac, opus and wma. Those four therefore share as application/octet-stream, never route to the audio player, and auto-file to Documents instead of Music. Shipping a music library while the share path still mis-types the entire AAC family would be shipping a library that only half works.

Output: adaptLibraryTracks in the content adapter, the songs bucket fed from music.*, and a corrected share MIME map.

<flagged_assumptions> None in this plan. </flagged_assumptions>

<artifacts_this_phase_produces> Symbols created by this plan:

  • neode-ui/src/composables/archyContentAdapter.ts: export function adaptLibraryTracks, export function adaptLibraryAlbums, export interface ArchyLibraryTrack, export interface ArchyLibraryAlbum
  • /home/archipelago/Projects/AIUI/packages/app/src/composables/useArchy.ts: requestArchyLibrary

Changed, not created: ShareModal.vue's existing extension-to-MIME map gains four entries. No new component, no new postMessage channel, and no change to SongGrid.vue. </artifacts_this_phase_produces>

<execution_context> @$HOME/.claude/gsd-core/workflows/execute-plan.md @$HOME/.claude/gsd-core/templates/summary.md </execution_context>

@.planning/PROJECT.md @.planning/STATE.md @CLAUDE.md @.planning/phases/13-aiui-functional-conversational-node-control-and-content-surf/13-CONTEXT.md @.planning/phases/13-aiui-functional-conversational-node-control-and-content-surf/13-MUSIC-MODEL.md @.planning/phases/13-aiui-functional-conversational-node-control-and-content-surf/13-06-SUMMARY.md @.planning/phases/13-aiui-functional-conversational-node-control-and-content-surf/13-07-SUMMARY.md Task 1: Map the library onto the Song shape the grid already renders neode-ui/src/composables/archyContentAdapter.ts, neode-ui/src/composables/__tests__/archyContentAdapter.test.ts - A `music.list-tracks` record becomes a `Song` with title, artist, album and duration carried through from the extracted tags. - A track whose artist tag was absent maps to a display value derived from the album artist, or to an empty string — never to the literal `null` or `undefined`. - Album grouping preserves the index's deterministic order; calling the adapter twice on the same input yields the same order. - A track with no cover art maps with an absent `coverUrl`, and the grid's existing no-artwork state is what renders — no broken-image URL is emitted. - A paid or peer-sourced track carries a source entry distinguishing it from an own-library track, using the same three source literals 13-06 pinned. - No produced playback URL carries a credential in its query string. - An empty library produces an empty `songs` array, not `undefined`. - `neode-ui/src/composables/archyContentAdapter.ts` (13-06) — `adaptContentItems`, `classifyByMime`, `sortDeterministic`, and the three pinned source literals. **Extend this file's conventions; the library mapping is a sibling of the ContentItem mapping, not a replacement.** - `neode-ui/src/composables/__tests__/archyContentAdapter.test.ts` (13-06) — including the assertion that no adapter-produced URL matches a credential query parameter. The new mapping is held to the same assertion. - `/home/archipelago/Projects/AIUI/packages/core/src/types/content.ts` lines 44-60 — `Song` and `SongSource`, the exact target shape. **Read, never modify** (D-12). - `core/archipelago/src/api/rpc/music.rs` (13-07) — the `music.list-albums` / `music.list-tracks` response envelopes. - `.planning/phases/13-.../13-MUSIC-MODEL.md` — the entity model whose field names the adapter reads. - `neode-ui/src/composables/useAudioPlayer.ts` and `neode-ui/src/components/GlobalAudioPlayer.vue` — the singleton bottom-bar player. **Audio never opens the lightbox**; that rule is enforced in five existing call sites and the new path must not become a sixth exception. Add `adaptLibraryTracks` and `adaptLibraryAlbums` to `archyContentAdapter.ts`, mapping the `music.*` response records onto AIUI's `Song` shape. Reuse `sortDeterministic`'s comparator idea but honour the index's own ordering, which 13-07 already made stable — do not re-sort by a different key in the browser, or the grid and the RPC will disagree about what "first" means.

Missing tag fields map to a display fallback (album artist, then empty string) rather than to a stringified null. A track with no cover art gets no coverUrl at all, so SongGrid's existing no-artwork state renders instead of a broken image — this matters because AIUI's cover-art sources are dev-server-only Vite middleware and are 404 on a node, which 13-06 already recorded as a known and accepted gap.

Playback URLs resolve through the existing content endpoints for own-library tracks and the existing Rust Range-streaming proxy for peer tracks. Do not build a URL with a credential in its query string — the same rule and the same test assertion as 13-06.

Feed the results into the existing generic content:push channel's songs bucket by adding a kind value; do not add a second channel and do not modify contextBroker.ts's transport. That is what 13-06's kind discriminator was for, and it is what keeps this plan's files_modified from colliding with the control track.

Extend archyContentAdapter.test.ts with a test per <behavior> bullet, including a repeat of the credential-in-URL assertion over the new mapping's output. cd neode-ui && npx vitest run src/composables/tests/archyContentAdapter.test.ts cd neode-ui && npx vitest run src/composables/tests/useAudioPlayer.test.ts cd neode-ui && npx vue-tsc --noEmit <acceptance_criteria>

  • cd neode-ui && npx vitest run src/composables/__tests__/archyContentAdapter.test.ts exits 0 with a test per <behavior> bullet and the existing 13-06 tests still passing
  • grep -q 'export function adaptLibraryTracks' neode-ui/src/composables/archyContentAdapter.ts
  • grep -cE '[?&](auth|token)=' neode-ui/src/composables/archyContentAdapter.ts returns 0
  • cd neode-ui && git diff --exit-code -- src/services/contextBroker.ts exits 0 — the transport was not touched; the kind discriminator absorbed the new bucket
  • git -C /home/archipelago/Projects/AIUI diff --exit-code -- packages/app/src/components/content/SongGrid.vue packages/core/src/types/content.ts exits 0
  • cd neode-ui && npx vitest run src/composables/__tests__/useAudioPlayer.test.ts exits 0 — the audio-never-in-lightbox rule is still pinned
  • cd neode-ui && npx vue-tsc --noEmit exits 0 </acceptance_criteria> A prop-shaped mapping over a stable RPC surface; the source behind the grid stays swappable per D-12. Real indexed tracks render in SongGrid through its unchanged props, with stable order, honest empty states, no broken cover images and no credential-bearing URL.
Task 2: Four missing audio types — the AAC family stops being filed as Documents neode-ui/src/components/cloud/ShareModal.vue - Sharing a `.m4a` produces an audio MIME type, not the generic binary type. - The same holds for `.aac`, `.opus` and `.wma`. - A shared file with an audio MIME routes to the global bottom-bar player and does not open the lightbox. - A shared file with an audio MIME auto-files to Music rather than Documents. - Existing behaviour for `.mp3`, `.flac`, `.ogg` and `.wav` is unchanged. - An unknown extension still falls back to the generic binary type — the fix adds entries, it does not guess. - `neode-ui/src/components/cloud/ShareModal.vue` around line 358 — the extension-to-MIME map that today lists exactly four audio extensions. Read the surrounding function to see how the fallback works before adding entries. - `neode-ui/src/composables/useAudioPlayer.ts` and `neode-ui/src/components/GlobalAudioPlayer.vue` — how a MIME type routes a file to the bottom-bar player, and the five call sites that enforce audio-never-in-lightbox. - `core/archipelago/src/api/rpc/content.rs` around line 668 — the node-side MIME auto-filing that decides Music vs Documents. The browser-side map must agree with it, or a file will play correctly and file wrongly. - `neode-ui/src/composables/archyContentAdapter.ts` — `classifyByMime` from 13-06 already handles these four extensions. **Keep the two lists consistent**; a divergence here is exactly how this bug survived the first time. Add the four missing entries to the extension-to-MIME map in `ShareModal.vue`: the AAC-in-MP4 container extension, raw AAC, Opus, and Windows Media Audio, each mapped to its correct audio MIME type. Leave the existing four entries and the generic fallback exactly as they are — this fix adds coverage, it does not change the fallback strategy and it does not guess at unknown extensions.

Cross-check the resulting map against classifyByMime in archyContentAdapter.ts and against the node-side auto-filing in content.rs, and make the three agree. If they disagree on any of the eight audio extensions, record which one is authoritative in the summary and align the other two to it — a browser that plays a file correctly while the node files it under Documents is the same class of bug in a new place.

Add a test asserting the mapping for all eight audio extensions plus one unknown extension, and one asserting that an audio MIME does not open the lightbox. Place it alongside the existing cloud-component tests, following neode-ui/src/composables/__tests__/useFileType.test.ts's fixture-table convention. cd neode-ui && npx vitest run src/composables/tests/useAudioPlayer.test.ts cd neode-ui && npx vitest run 2>&1 | tail -15 cd neode-ui && npx vue-tsc --noEmit <acceptance_criteria>

  • grep -cE "m4a:|aac:|opus:|wma:" neode-ui/src/components/cloud/ShareModal.vue returns 4
  • grep -cE "mp3:|flac:|ogg:|wav:" neode-ui/src/components/cloud/ShareModal.vue returns 4 — the existing entries survived
  • A new test asserts the MIME for all eight audio extensions and for one unknown extension, and it passes
  • A test asserts an audio MIME does not open the lightbox, and it passes
  • cd neode-ui && npx vitest run exits 0 — the whole neode-ui suite is green
  • The summary records which of the three MIME maps (ShareModal.vue, classifyByMime, content.rs) was taken as authoritative and confirms the other two agree on all eight extensions </acceptance_criteria> All eight common audio extensions get a real audio MIME on the share path, route to the bottom-bar player, and file to Music; unknown extensions still fall back rather than being guessed.
Task 3: AIUI asks for the library the same way it asks for content /home/archipelago/Projects/AIUI/packages/app/src/composables/useArchy.ts - `/home/archipelago/Projects/AIUI/packages/app/src/composables/useArchy.ts` — `requestArchyContent` from 13-06 and the `archyBridge.requestContext` convention it mirrors. **Add a sibling; do not invent a fourth transport convention.** - `/home/archipelago/Projects/AIUI/packages/app/src/composables/useContentPanel.ts` — `setArchyContent` and `archyContentActive` from 13-06; the `songs` bucket is what this feeds. - `/home/archipelago/Projects/AIUI/packages/app/src/pages/ChatPage.vue` — the live render tree through `ContentGridView`. **`ContentPanel.vue` is dead code and must not be built through.** Work in `/home/archipelago/Projects/AIUI` on branch `development`.

Add requestArchyLibrary(scope) to useArchy.ts as a sibling of 13-06's requestArchyContent, using the same bridge call with the library kind. Route its response through the existing setArchyContent so the songs bucket fills exactly the way the films bucket already does.

Do not modify SongGrid.vue, ContentGridView.vue or packages/core/src/types/content.ts — D-12 keeps AIUI's design exactly and only the data source changes. Do not revive ContentPanel.vue or any component that only it referenced.

Record honestly in the summary that album artwork is absent for library tracks on a node, because AIUI's artwork sources are dev-server-only Vite middleware, and that SongGrid renders its existing no-artwork state rather than a broken image.

Commit and push on development, staging explicitly by path. cd /home/archipelago/Projects/AIUI/packages/app && npx vitest run cd /home/archipelago/Projects/AIUI/packages/app && npx vue-tsc --noEmit cd /home/archipelago/Projects/AIUI && git status --porcelain | grep -c . | grep -qx 0 <acceptance_criteria>

  • grep -q 'requestArchyLibrary' /home/archipelago/Projects/AIUI/packages/app/src/composables/useArchy.ts
  • grep -c 'ContentPanel' /home/archipelago/Projects/AIUI/packages/app/src/composables/useArchy.ts returns 0
  • git -C /home/archipelago/Projects/AIUI diff --exit-code HEAD~1 -- packages/app/src/components/content/ packages/core/src/types/content.ts exits 0
  • cd /home/archipelago/Projects/AIUI/packages/app && npx vitest run exits 0
  • cd /home/archipelago/Projects/AIUI/packages/app && npx vue-tsc --noEmit exits 0
  • The commit is pushed to development and the working tree is clean </acceptance_criteria> AIUI requests the library over the same bridge it uses for content, and SongGrid fills from real indexed tracks with no grid component changed.

<threat_model>

Trust Boundaries

Boundary Description
tag text → the browser DOM Peer-authored ID3/Vorbis tag strings render as track titles and artist names
library records → iframe Node data crossing into AIUI, gated on the media grant like all content
shared file MIME → player and filing A wrong MIME misroutes both playback and storage location

STRIDE Threat Register

Threat ID Category Component Severity Disposition Mitigation Plan
T-13-70 Tampering Peer-authored tag text rendered as markup medium mitigate Vue interpolation escapes by default; the adapter emits plain strings and no raw-HTML directive is introduced, matching 13-06
T-13-71 Information Disclosure A credential in a track playback URL high mitigate Same rule and same test assertion as 13-06: no credential query parameter is produced. Own tracks use the session-carrying content endpoints, peer tracks the existing Rust Range proxy
T-13-72 Tampering Browser and node MIME maps disagreeing, so a file plays right and files wrong medium mitigate Task 2 cross-checks all three maps and records which is authoritative; the divergence is what produced the original m4a/aac/opus/wma bug
T-13-73 Denial of Service An unbounded library pulled into the browser in one push low mitigate 13-07's limit clamp applies; the adapter consumes the paginated envelope rather than requesting everything
T-13-74 Elevation of Privilege Library records reaching the iframe without a media grant high mitigate The existing content:push handler's permission check from 13-06 applies unchanged — this plan adds a kind, not a bypass
T-13-75 Repudiation Audio opening in the lightbox, breaking a rule enforced in five call sites low mitigate useAudioPlayer.test.ts is re-run as an acceptance criterion, and Task 2 adds an explicit assertion
T-13-SC Tampering npm/pip/cargo installs high mitigate Zero packages added in either repo. No install task, so no legitimacy checkpoint required
</threat_model>
- `cd neode-ui && npx vitest run` green (whole suite, including `archyContentAdapter.test.ts` and `useAudioPlayer.test.ts`) - `cd neode-ui && git diff --exit-code -- src/services/contextBroker.ts` exits 0 - `cd /home/archipelago/Projects/AIUI/packages/app && npx vitest run && npx vue-tsc --noEmit` green - All eight audio extensions map to an audio MIME across `ShareModal.vue`, `classifyByMime` and `content.rs`

<success_criteria> SongGrid renders the node's real library through unchanged props, the AAC family stops being filed as Documents and stops missing the audio player, and the music track reached the UI without a single control-track or content-track plan depending on it. </success_criteria>

Create `.planning/phases/13-aiui-functional-conversational-node-control-and-content-surf/13-11-SUMMARY.md` when done