const { test } = require('node:test'); const assert = require('node:assert/strict'); const fs = require('node:fs'); const path = require('node:path'); const vm = require('node:vm'); const ROOT = path.join(__dirname, '..', '..'); const INSPECTOR_JS = path.join(ROOT, 'plugins', 'capability_inspector', 'screen.js'); function makeElement(id) { const element = { id, innerHTML: '', textContent: '', value: '', dataset: {}, children: [], listeners: {}, classList: { values: new Set(), add(name) { this.values.add(name); }, remove(name) { this.values.delete(name); }, contains(name) { return this.values.has(name); }, }, appendChild(child) { this.children.push(child); return child; }, addEventListener(type, handler) { this.listeners[type] = handler; }, }; return element; } function loadInspector(snapshot, options = {}) { class CustomEvent { constructor(type, init = {}) { this.type = type; this.detail = init.detail; } } const listeners = new Map(); const elements = new Map([ ['capability-inspector-filter', makeElement('capability-inspector-filter')], ['capability-inspector-content', makeElement('capability-inspector-content')], ['capability-inspector-empty', makeElement('capability-inspector-empty')], ['capability-inspector-summary', makeElement('capability-inspector-summary')], ['capability-inspector-refresh', makeElement('capability-inspector-refresh')], ]); const window = { console, CustomEvent, setTimeout(callback) { callback(); return 1; }, clearTimeout() {}, addEventListener(type, handler) { const list = listeners.get(type) || []; list.push(handler); listeners.set(type, list); }, dispatchEvent(event) { for (const handler of (listeners.get(event.type) || []).slice()) handler(event); return true; }, feedBack: { capabilities: { snapshotDiagnostics: () => (typeof snapshot === 'function' ? snapshot() : snapshot), }, playback: options.playbackSnapshot ? { snapshot: () => options.playbackSnapshot, } : undefined, }, navigator: { clipboard: { writeText: async () => {} } }, document: { readyState: 'complete', getElementById(id) { return elements.get(id) || null; }, createElement(tagName) { return makeElement(tagName); }, addEventListener() {}, }, }; window.window = window; window.globalThis = window; window.__listeners = listeners; const context = vm.createContext(window); vm.runInContext(fs.readFileSync(INSPECTOR_JS, 'utf8'), context, { filename: INSPECTOR_JS }); return { window, elements }; } test('capability inspector renders playback session route loop bridges and outcomes', () => { const snapshot = { pipelines: [{ name: 'playback', review: { lifecycle: 'active', label: 'Active contract', tone: 'clean', summary: 'Core playback facade.' }, participants: [{ pluginId: 'core', roles: ['owner'], commands: ['inspect'], events: ['ready'], runtime: true, availability: 'available' }], conflicts: [] }], participants: [{ pluginId: 'core' }], compatibilityShims: [], expectedCompatibilityShims: [], }; const playbackSnapshot = { schema: 'feedBack.playback.diagnostics.v1', state: { sessionId: 'playback-1', state: 'playing', target: { targetId: 'target-abc', localDisplay: { title: 'Song', artist: 'Artist', arrangement: 'Lead' } }, media: { currentTime: 12.5, duration: 90, route: { routeKind: 'browser-media', state: 'active' }, loop: { enabled: true, startTime: 10, endTime: 20, state: 'active' } }, route: { routeKind: 'browser-media', state: 'active', safeReason: 'browser media route active' }, loop: { enabled: true, startTime: 10, endTime: 20, state: 'active' }, }, participants: [{ requesterId: 'plugin.practice' }, { observerId: 'plugin.hud' }], bridges: [{ bridgeId: 'playback.window-play-song', hitCount: 2 }], history: { current: { recentOutcomes: [{ operation: 'seek', status: 'completed' }], lifecycleEvents: [{ event: 'playback:seeked', state: 'playing' }] } }, }; const { elements } = loadInspector(snapshot, { playbackSnapshot }); const content = elements.get('capability-inspector-content').innerHTML; assert.match(content, /data-playback-support/); assert.match(content, /Session: playback-1/); assert.match(content, /Target: Song - Artist/); assert.match(content, /Route: browser-media \(active\)/); assert.match(content, /playback\.window-play-song:2/); assert.match(content, /seek:completed/); }); test('capability inspector renders shims inside their capability domain', () => { const snapshot = { pipelines: [ { name: 'library', review: { lifecycle: 'active', label: 'Active contract', tone: 'clean', summary: 'Current library surface.' }, participants: [ { pluginId: 'core', roles: ['owner'], commands: ['list-providers', 'refresh-providers', 'get-current', 'select-provider', 'sync-song', 'inspect'], operations: ['query-page', 'query-artists', 'query-stats', 'tuning-names', 'get-art', 'sync-song'], events: ['providers-refreshed', 'source-changed', 'song-sync-started', 'song-sync-succeeded', 'song-sync-failed'], description: 'Owns the library provider registry and dispatches source selection, browsing, and song sync commands.', runtime: true, availability: 'available', ownership: 'multi-provider', safety: 'safe' }, { pluginId: 'local', roles: ['provider'], operations: ['query-page', 'query-artists', 'query-stats', 'tuning-names', 'get-art'], events: ['providers-refreshed', 'source-changed'], runtime: true, availability: 'available', ownership: 'multi-provider', safety: 'safe', providerPolicy: { providerId: 'local', kind: 'local', default: true } }, { pluginId: 'remote_library_client', roles: ['provider'], operations: ['query-page', 'query-artists', 'query-stats', 'tuning-names', 'get-art', 'sync-song'], events: ['providers-refreshed', 'source-changed', 'song-sync-started', 'song-sync-succeeded', 'song-sync-failed'], runtime: true, availability: 'available', ownership: 'multi-provider', safety: 'safe', providerPolicy: { providerId: 'remote:client', kind: 'remote', ownerPluginId: 'remote_library_client' } }, { pluginId: 'remote_library_server', roles: ['requester', 'observer'], commands: ['list-providers', 'get-current', 'inspect'], events: ['providers-refreshed', 'source-changed'], description: 'Wraps the local library source for direct remote-library clients.', runtime: false, availability: 'available', ownership: 'requester-only', safety: 'safe' }, ], conflicts: [] }, { name: 'custom.practice', review: { lifecycle: 'plugin-defined', label: 'Plugin-defined', tone: 'info', summary: 'Plugin-specific practice surface.' }, participants: [{ pluginId: 'practice_hud', roles: ['observer'], commands: [], runtime: true, availability: 'available', ownership: 'observer-only', safety: 'safe' }], conflicts: [] }, { name: 'backend.routes', review: { lifecycle: 'future-expansion', label: 'Future expansion', tone: 'warning', summary: 'Backend route bridge.' }, participants: [{ pluginId: 'core', roles: ['owner'], commands: ['inspect'], runtime: true, availability: 'available', ownership: 'multi-provider', safety: 'privileged' }], conflicts: [] }, { name: 'playback', review: { lifecycle: 'active', label: 'Active contract', tone: 'clean', summary: 'Core playback facade.' }, participants: [ { pluginId: 'core', roles: ['owner', 'provider'], commands: ['play', 'pause', 'seek', 'snapshot'], events: ['song:ready', 'song:seek', 'beats:loaded', 'arrangement:changed'], description: 'Owns player transport commands and lifecycle events.', runtime: true, availability: 'available', ownership: 'exclusive-owner', safety: 'safe' }, { pluginId: 'plugin_1', roles: ['observer'], commands: [], events: ['song:ready', 'beats:loaded'], runtime: true, availability: 'available', ownership: 'exclusive-owner', safety: 'safe' }, { pluginId: 'plugin_2', roles: ['requester'], commands: ['play'], events: ['song:ready', 'arrangement:changed'], runtime: true, availability: 'available', ownership: 'exclusive-owner', safety: 'safe' }, { pluginId: 'plugin_3', roles: ['participant'], commands: ['pause', 'seek'], events: ['song:seek'], runtime: true, availability: 'available', ownership: 'exclusive-owner', safety: 'safe' }, ], conflicts: [] }, { name: 'audio-effects', review: { lifecycle: 'active', label: 'Active contract', tone: 'clean', summary: 'Core audio-effects facade.' }, participants: [ { pluginId: 'core.audio.effects', roles: ['owner'], commands: ['register-provider', 'register-executor', 'resolve-plan', 'load-plan', 'inspect'], events: ['provider-registered', 'executor-registered'], description: 'Owns the provider and executor registry.', runtime: true, availability: 'available', ownership: 'multi-provider', safety: 'sensitive' }, { pluginId: 'nam-tone', roles: ['provider', 'executor'], operations: ['chain.resolve', 'executor.load-chain-plan'], events: ['plan-resolved'], description: 'Provides NAM fallback plans and browser execution.', runtime: true, availability: 'available', ownership: 'multi-provider', safety: 'sensitive' }, ], conflicts: [] }, { name: 'diagnostics', review: { lifecycle: 'diagnostic', label: 'Snapshot surface', tone: 'info', summary: 'Read-only diagnostics snapshot/export facade for support bundles and the Capability Inspector.' }, participants: [ { pluginId: 'core', roles: ['owner', 'provider'], commands: ['snapshot'], events: [], description: 'Provides read-only capability snapshots for support bundles and the Capability Inspector.', runtime: true, availability: 'available', ownership: 'diagnostic-only', safety: 'diagnostic-only' }, { pluginId: 'capability_inspector', roles: ['requester'], commands: ['snapshot'], events: [], runtime: true, availability: 'available', ownership: 'diagnostic-only', safety: 'diagnostic-only' }, ], conflicts: [] }, { name: 'pipeline', review: { lifecycle: 'diagnostic', label: 'Graph controls', tone: 'info', summary: 'Capability graph operations: resolve, inspect, validate, and enable or disable participants.' }, participants: [ { pluginId: 'core', roles: ['owner', 'provider'], commands: ['resolve', 'inspect', 'validate', 'participant.set-enabled'], events: ['resolved', 'runtime.validated', 'participant.state-changed'], description: 'Owns capability graph inspection, validation, resolution, and participant enablement commands.', runtime: true, availability: 'available', ownership: 'diagnostic-only', safety: 'diagnostic-only' }, { pluginId: 'capability_inspector', roles: ['requester', 'observer'], commands: ['inspect', 'validate'], events: ['runtime.validated', 'participant.state-changed'], runtime: true, availability: 'available', ownership: 'diagnostic-only', safety: 'diagnostic-only' }, ], conflicts: [] }, ], participants: [{ pluginId: 'core' }, { pluginId: 'remote_library_client' }, { pluginId: 'remote_library_server' }], compatibilityShims: [ { shimId: 'remote_library_client:register_library_provider:library:remote:client', source: 'remote_library_client', capability: 'library', legacySurface: 'register_library_provider', status: 'used', hitCount: 3, providerId: 'remote:client', reason: 'legacy backend register_library_provider() registered provider', lastHitAt: '2026-05-24T00:00:00.000Z', }, { shimId: 'runtime:library:refresh:window.feedBack.libraryProviders.refresh', source: 'window.feedBack.libraryProviders.refresh', capability: 'library', legacySurface: 'refresh', status: 'used', hitCount: 1, reason: 'Legacy library provider refresh invoked', lastHitAt: '2026-05-24T00:01:00.000Z', }, ], expectedCompatibilityShims: [ { capability: 'library', legacySurface: 'register_library_provider', reason: 'legacy backend provider registration becomes a library participant', }, { capability: 'library', legacySurface: 'refresh', reason: 'legacy library provider client refresh calls are counted as library.refresh command use', }, ], }; const { window, elements } = loadInspector(snapshot); const content = elements.get('capability-inspector-content').innerHTML; const summary = elements.get('capability-inspector-summary').innerHTML; const filterElement = elements.get('capability-inspector-filter'); const filter = filterElement.innerHTML; assert.match(summary, /Domains/); assert.match(summary, /Participants/); assert.match(summary, /data-summary-dashboard/); assert.match(summary, /Shim hit<\/span>\s*1<\/span>/); assert.doesNotMatch(summary, />Shim hits<\/span>\s*4<\/span>/); assert.doesNotMatch(summary, /Legacy listeners/); assert.match(summary, /data-summary-card="status" data-tone="clean"/); assert.match(summary, /Clean<\/span>/); assert.match(filter, //); assert.match(filter, //); assert.match(filter, //); assert.match(filter, //); assert.ok(filter.indexOf('ui.navigation') === -1); assert.ok(filter.indexOf('library') < filter.indexOf('playback')); assert.ok(filter.indexOf('playback') < filter.indexOf('audio-effects')); assert.ok(filter.indexOf('audio-effects') < filter.indexOf('custom.practice')); assert.ok(filter.indexOf('playback') < filter.indexOf('custom.practice')); assert.ok(filter.indexOf('custom.practice') < filter.indexOf('diagnostics')); assert.ok(filter.indexOf('backend.routes') === -1); assert.doesNotMatch(content, /Domain group/); assert.match(content, /