(function () {
'use strict';
const state = window.__feedBackCapabilityInspector || (window.__feedBackCapabilityInspector = {});
state.render = render;
if (state.installed) return;
state.installed = true;
const DOCUMENTATION_ONLY_DOMAINS = new Set([
'backend.routes',
'ui.player-controls',
'ui.player-panels',
'ui.player-overlays',
'plugins',
'jobs',
'midi-control',
'tempo-clock',
]);
const DOMAIN_GROUPS = Object.freeze([
Object.freeze({
id: 'app-library',
label: 'Application and Library',
summary: 'Navigation, plugin screens, settings, library sources, and tunings.',
domains: Object.freeze(['ui.navigation', 'ui.plugin-screens', 'settings', 'library', 'tuning']),
}),
Object.freeze({
id: 'player-audio',
label: 'Player and Audio Runtime',
summary: 'Playback, renderer, chart-transform, mixer, monitoring, effects, and note-detection surfaces.',
domains: Object.freeze(['playback', 'visualization', 'chart-transform', 'audio-mix', 'audio-input', 'audio-monitoring', 'audio-effects', 'stems', 'note-detection']),
}),
Object.freeze({
id: 'plugin-defined',
label: 'Plugin-defined Domains',
summary: 'Domains declared by installed plugins outside the current core surface list.',
domains: Object.freeze([]),
}),
Object.freeze({
id: 'capability-runtime',
label: 'Capability Runtime',
summary: 'Diagnostics snapshots plus capability graph operations used by the inspector.',
domains: Object.freeze(['diagnostics', 'pipeline']),
}),
]);
const DOMAIN_ICONS = Object.freeze({
library: 'bookOpen',
playback: 'playCircle',
visualization: 'monitor',
'audio-mix': 'sliders',
'audio-effects': 'box',
'audio-monitoring': 'headphones',
stems: 'sliders',
'note-detection': 'activity',
'chart-transform': 'box',
diagnostics: 'fileSearch',
pipeline: 'activity',
'ui.navigation': 'list',
'ui.plugin-screens': 'monitor',
settings: 'sliders',
'backend.routes': 'network',
'ui.player-controls': 'sliders',
'ui.player-panels': 'monitor',
'ui.player-overlays': 'monitor',
plugins: 'puzzle',
jobs: 'history',
'midi-control': 'sliders',
'audio-input': 'plug',
'tempo-clock': 'history',
});
const ICON_PATHS = Object.freeze({
activity: ' ',
alert: ' ',
bookOpen: ' ',
box: ' ',
chevronDown: ' ',
chevronRight: ' ',
circleOff: ' ',
coreCube: ' ',
crown: ' ',
eye: ' ',
fileSearch: ' ',
flag: ' ',
headphones: ' ',
history: ' ',
info: ' ',
list: ' ',
lock: ' ',
monitor: ' ',
network: ' ',
playCircle: ' ',
plug: ' ',
puzzle: ' ',
send: ' ',
shield: ' ',
shieldAlert: ' ',
sliders: ' ',
users: ' ',
});
const COMMAND_ICON = ' ';
const OPERATION_ICON = ' ';
const EVENT_ICON = ' ';
const PROVIDER_COMMAND_LINK = ' ';
const PROVIDER_OPERATION_LINK = ' ';
const PROVIDER_EVENT_LINK = ' ';
const GRAPH_FILTERS = new Set(['all', 'commands', 'operations', 'events', 'shimmed']);
const LEGACY_SURFACE_ENDPOINTS = Object.freeze({
library: Object.freeze({
refresh: Object.freeze({ type: 'command', label: 'refresh' }),
select: Object.freeze({ type: 'command', label: 'select' }),
'sync-song': Object.freeze({ type: 'command', label: 'sync-song' }),
}),
});
const ATTRIBUTION_ONLY_SHIMS = Object.freeze({
library: new Set(['register_library_provider']),
});
const CYTOSCAPE_URL = '/static/vendor/cytoscape/cytoscape.min.js';
let cytoscapePromise = null;
let activeGraph = null;
let activeGraphs = new Map();
let renderScheduled = false;
function text(value) {
return String(value == null ? '' : value).replace(/[<>&]/g, ch => ({ '<': '<', '>': '>', '&': '&' }[ch]));
}
function attr(value) {
return text(value).replace(/["']/g, ch => ({ '"': '"', "'": ''' }[ch]));
}
function registry() {
return window.feedBack && window.feedBack.capabilities;
}
function snapshot() {
const api = registry();
if (!api || typeof api.snapshotDiagnostics !== 'function') return null;
try { return api.snapshotDiagnostics(); }
catch (err) { return { error: err && err.message ? err.message : String(err) }; }
}
function chips(values, className) {
const list = Array.isArray(values) ? values : [];
return list.map(item => `${text(item)} `).join('');
}
function pill(label, tone = 'muted') {
const classes = {
clean: 'bg-emerald-500/10 text-emerald-300 border-emerald-700/60',
observed: 'bg-teal-500/10 text-teal-300 border-teal-700/60',
used: 'bg-gold/10 text-gold border-gold/50',
warning: 'bg-amber-500/10 text-amber-300 border-amber-700/60',
conflict: 'bg-red-500/10 text-red-300 border-red-800/70',
muted: 'bg-dark-700 text-gray-400 border-gray-800',
info: 'bg-accent/10 text-accent-light border-accent/40',
};
return `${text(label)} `;
}
function iconSvg(name, className = 'h-4 w-4') {
const path = ICON_PATHS[name] || ICON_PATHS.info;
return `${path} `;
}
function iconBadge(iconName, label, tooltip, tone = 'muted') {
const classes = {
clean: 'bg-emerald-500/10 text-emerald-300 border-emerald-700/60',
observed: 'bg-teal-500/10 text-teal-300 border-teal-700/60',
used: 'bg-gold/10 text-gold border-gold/50',
warning: 'bg-amber-500/10 text-amber-300 border-amber-700/60',
conflict: 'bg-red-500/10 text-red-300 border-red-800/70',
muted: 'bg-dark-700 text-gray-400 border-gray-800',
info: 'bg-accent/10 text-accent-light border-accent/40',
};
const title = tooltip || label;
return `${iconSvg(iconName)}${text(label)} `;
}
function compactIconBadge(iconName, value, tooltip, tone = 'muted') {
const classes = {
clean: 'bg-emerald-500/10 text-emerald-300 border-emerald-700/60',
observed: 'bg-teal-500/10 text-teal-300 border-teal-700/60',
used: 'bg-gold/10 text-gold border-gold/50',
warning: 'bg-amber-500/10 text-amber-300 border-amber-700/60',
conflict: 'bg-red-500/10 text-red-300 border-red-800/70',
muted: 'bg-dark-700 text-gray-400 border-gray-800',
info: 'bg-accent/10 text-accent-light border-accent/40',
};
const hasValue = value !== '' && value != null;
return `${iconSvg(iconName)}${hasValue ? `${text(value)} ` : ''} `;
}
function availabilityBadge(availability) {
const value = availability || 'available';
const tone = value === 'available' ? 'clean' : (value === 'disabled' ? 'muted' : 'warning');
const icon = value === 'available' ? 'shield' : (value === 'disabled' ? 'circleOff' : 'shieldAlert');
const classes = {
clean: 'bg-emerald-500/10 text-emerald-300 border-emerald-700/60',
warning: 'bg-amber-500/10 text-amber-300 border-amber-700/60',
muted: 'bg-dark-700 text-gray-400 border-gray-800',
};
const title = `Availability: ${value}`;
return `${iconSvg(icon, 'h-4 w-4')} `;
}
function roleIconBadge(iconName, title, tone = 'muted', role = '') {
const classes = {
owner: 'bg-gold/10 text-gold border-gold/50',
provider: 'bg-accent/10 text-accent-light border-accent/40',
observer: 'bg-teal-500/10 text-teal-300 border-teal-700/60',
requester: 'bg-gold/10 text-gold border-gold/50',
muted: 'bg-dark-700 text-gray-400 border-gray-800',
};
return `${iconSvg(iconName, 'h-4 w-4')} `;
}
function originIconBadge(iconName, title, tone = 'muted', origin = '') {
const classes = {
core: 'bg-blue-500/10 text-blue-300 border-blue-700/60',
external: 'bg-dark-700 text-gray-400 border-gray-800',
muted: 'bg-dark-700 text-gray-400 border-gray-800',
};
return `${iconSvg(iconName, 'h-4 w-4')} `;
}
function participantOriginIcon(participant) {
const pluginId = participant && participant.pluginId;
if (!pluginId || pluginId === 'No owner/provider') return '';
const providerId = participantProviderId(participant);
const coreOrigin = pluginId === 'core' || String(pluginId).startsWith('core.') || providerId === 'local';
const roleLabel = hasRole(participant, 'owner')
? 'owner'
: ((providerId || hasRole(participant, 'provider')) ? 'provider' : 'participant');
const label = `${coreOrigin ? 'Core' : 'Non-core'} ${roleLabel}`;
return coreOrigin
? originIconBadge('coreCube', label, 'core', 'core')
: originIconBadge('puzzle', label, 'external', 'non-core');
}
function participantProviderId(participant) {
const policy = participant && participant.providerPolicy && typeof participant.providerPolicy === 'object' ? participant.providerPolicy : {};
return String(policy.providerId || '').trim();
}
function providerRoleIcon(participant) {
const providerId = participantProviderId(participant);
if (providerId || hasRole(participant, 'provider')) return roleIconBadge('plug', providerId ? `Provider: ${providerId}` : 'Provider participant', 'provider', 'provider');
return '';
}
function ownerHeaderIcons(participant) {
if (!participant) return '';
if (hasRole(participant, 'owner')) {
const kind = String(participant.kind || '').trim();
const ownership = String(participant.ownership || '').trim();
const secondaryIcon = kind === 'provider-coordinator' || ownership === 'multi-provider'
? roleIconBadge('network', 'Provider coordinator', 'provider', 'provider-coordinator')
: roleIconBadge('plug', 'Capability provider', 'provider', 'provider');
return `${roleIconBadge('crown', 'Owner', 'owner', 'owner')}${participantOriginIcon(participant)}${secondaryIcon}`;
}
return `${providerRoleIcon(participant)}${participantOriginIcon(participant)}`;
}
function participantTooltip(participant) {
const pluginId = participant && participant.pluginId ? participant.pluginId : 'unknown';
const roles = Array.isArray(participant && participant.roles) && participant.roles.length ? participant.roles.join(', ') : 'participant';
const parts = [participant && participant._shimOnly ? `Shim: ${pluginId}` : `Plugin: ${pluginId}`, `Roles: ${roles}`];
const providerId = participantProviderId(participant);
if (providerId) parts.push(`Provider: ${providerId}`);
parts.push(`Registration: ${participant && participant.runtime ? 'runtime' : 'manifest'}`);
parts.push(`Availability: ${participant && participant.availability ? participant.availability : 'available'}`);
if (participant && participant.safety) parts.push(`Safety: ${participant.safety}`);
if (participant && hasRole(participant, 'owner') && participant.kind) parts.push(`Kind: ${participant.kind}`);
if (participant && hasRole(participant, 'owner') && participant.ownership && !participant.kind) parts.push(`Ownership: ${participant.ownership}`);
if (participant && participant._shimOnly) parts.push('Source: legacy compatibility shim');
return parts.join(' | ');
}
function copyButton(value, label = 'Copy') {
if (!value) return '';
return `${text(label)} `;
}
function metricCard(label, value, tone = 'muted') {
const toneClass = {
clean: 'border-emerald-800/60 bg-emerald-500/5',
warning: 'border-amber-800/60 bg-amber-500/5',
conflict: 'border-red-800/70 bg-red-500/5',
used: 'border-gold/40 bg-gold/5',
muted: 'border-gray-800 bg-dark-800/70',
}[tone] || 'border-gray-800 bg-dark-800/70';
return `
${text(label)}
${text(value)}
`;
}
function isAttributionOnlyShim(shim) {
const attributionOnly = ATTRIBUTION_ONLY_SHIMS[String(shim && shim.capability || '')];
return !!(attributionOnly && attributionOnly.has(String(shim && shim.legacySurface || '')));
}
function totalShimHits(shims) {
return (Array.isArray(shims) ? shims : []).reduce((sum, shim) => (
isAttributionOnlyShim(shim) ? sum : sum + Number(shim && shim.hitCount || 0)
), 0);
}
function compatibilityState(conflicts, warningCount = 0) {
const count = Array.isArray(conflicts) ? conflicts.length : 0;
if (count) return { label: 'Conflicts', tone: 'conflict' };
if (warningCount) return { label: 'Warnings', tone: 'warning' };
return { label: 'Clean', tone: 'clean' };
}
function reviewInfo(pipeline) {
const review = pipeline && pipeline.review && typeof pipeline.review === 'object' ? pipeline.review : {};
return {
lifecycle: review.lifecycle || 'plugin-defined',
label: review.label || 'Plugin-defined',
tone: review.tone || 'info',
summary: review.summary || 'Declared by a plugin or test fixture rather than registered as a core FeedBack domain.',
};
}
function uniqueValues(items, field, fallback) {
const values = [];
for (const item of Array.isArray(items) ? items : []) {
const value = String(item && item[field] || fallback || '').trim();
if (value && !values.includes(value)) values.push(value);
}
if (!values.length && fallback) values.push(fallback);
return values;
}
function hasRole(participant, role) {
return Array.isArray(participant && participant.roles) && participant.roles.includes(role);
}
function isCoreOwner(participant) {
return participant && participant.pluginId === 'core' && hasRole(participant, 'owner');
}
function coreOwners(participants) {
return (Array.isArray(participants) ? participants : []).filter(isCoreOwner);
}
function nonCoreParticipants(participants) {
return (Array.isArray(participants) ? participants : []).filter(participant => participant && participant.pluginId !== 'core');
}
function ownerNames(participants) {
const owners = (Array.isArray(participants) ? participants : []).filter(participant => hasRole(participant, 'owner'));
return uniqueValues(owners, 'pluginId', '');
}
function ownerParticipants(participants) {
return (Array.isArray(participants) ? participants : []).filter(participant => hasRole(participant, 'owner'));
}
function uniqueList(values) {
const out = [];
for (const value of Array.isArray(values) ? values : []) {
const item = String(value || '').trim();
if (item && !out.includes(item)) out.push(item);
}
return out;
}
function capabilityItems(participants, field) {
const items = (Array.isArray(participants) ? participants : []).flatMap(participant => {
if (!participant) return [];
const values = Array.isArray(participant[field]) ? participant[field] : [];
if (field === 'events') return [...values, ...(Array.isArray(participant.observes) ? participant.observes : []), ...(Array.isArray(participant.emits) ? participant.emits : [])];
return values;
});
return uniqueList(items);
}
function shimEndpoint(surface, capability = '') {
const attributionOnly = ATTRIBUTION_ONLY_SHIMS[String(capability || '')];
if (attributionOnly && attributionOnly.has(String(surface || ''))) return null;
const mapped = (LEGACY_SURFACE_ENDPOINTS[String(capability || '')] || {})[String(surface || '')];
if (mapped) return mapped;
return { type: 'command', label: String(surface || '').trim() };
}
function shimEndpoints(shims, type, onlyHits) {
return uniqueList((Array.isArray(shims) ? shims : [])
.filter(shim => !onlyHits || Number(shim && shim.hitCount || 0) > 0)
.map(shim => shimEndpoint(shim && shim.legacySurface, shim && shim.capability))
.filter(endpoint => endpoint && endpoint.type === type)
.map(endpoint => endpoint.label)
.filter(Boolean));
}
function observedShimCommands(shims) {
return shimEndpoints(shims, 'command', true);
}
function observedShimEvents(shims) {
return shimEndpoints(shims, 'event', true);
}
function expectedShimCommands(expectedShims) {
return shimEndpoints(expectedShims, 'command', false);
}
function expectedShimEvents(expectedShims) {
return shimEndpoints(expectedShims, 'event', false);
}
function shimmedEndpointsBySource(shims) {
const bySource = new Map();
for (const shim of Array.isArray(shims) ? shims : []) {
if (!shim || Number(shim.hitCount || 0) <= 0) continue;
const source = String(shim.source || '').trim();
if (!source) continue;
const endpoint = shimEndpoint(shim.legacySurface, shim.capability);
if (!endpoint || !endpoint.label) continue;
const key = endpointKey(endpoint.type, endpoint.label);
const endpoints = bySource.get(source) || new Set();
endpoints.add(key);
bySource.set(source, endpoints);
}
return bySource;
}
function participantUses(participant, shims, expectedShims) {
const source = participant && participant.pluginId;
const participantShims = (Array.isArray(shims) ? shims : []).filter(shim => shim && shim.source === source);
const commands = uniqueList([...(participant && participant.commands || []), ...(participant && participant.requests || []), ...observedShimCommands(participantShims)]);
const operations = uniqueList([...(participant && participant.operations || [])]);
let events = uniqueList([...(participant && participant.events || []), ...(participant && participant.observes || []), ...(participant && participant.emits || []), ...observedShimEvents(participantShims)]);
if (!events.length && participant && hasRole(participant, 'observer')) events = expectedShimEvents(expectedShims);
return { commands, operations, events };
}
function currentGraphFilter() {
const value = String(state.domainGraphFilter || 'all');
return GRAPH_FILTERS.has(value) ? value : 'all';
}
function filteredCapabilities(commands, operations, events, filter) {
if (filter === 'commands') return { commands, operations: [], events: [] };
if (filter === 'operations') return { commands: [], operations, events: [] };
if (filter === 'events') return { commands: [], operations: [], events };
return { commands, operations, events };
}
function filteredGraphLinks(linkData, filter) {
if (filter === 'shimmed') return { ...linkData, links: linkData.links.filter(link => link.type === 'shimmed') };
if (filter === 'commands') return { ...linkData, links: linkData.links.filter(link => link.endpointType === 'command') };
if (filter === 'operations') return { ...linkData, links: linkData.links.filter(link => link.endpointType === 'operation') };
if (filter === 'events') return { ...linkData, links: linkData.links.filter(link => link.endpointType === 'event') };
return linkData;
}
function graphFilterButton(value, label) {
const active = currentGraphFilter() === value;
const classes = active ? 'bg-purple-500/40 text-white border-purple-400/40' : 'border-gray-800 text-gray-400 hover:text-white hover:border-gray-600';
return `${text(label)} `;
}
function endpointKey(type, value) {
return `${type}:${String(value || '')}`;
}
function participantEndpointKey(index, type, value) {
return `${index}:${endpointKey(type, value)}`;
}
function graphCollapsedGroups() {
state.graphCollapsedGroups = state.graphCollapsedGroups || {};
return state.graphCollapsedGroups;
}
function graphGroupStateKey(domain, side, index, type) {
return [domain || 'unknown', side || 'provider', index == null ? 'all' : String(index), type || 'event'].join('|');
}
function isGraphGroupCollapsed(domain, side, index, type) {
return !!graphCollapsedGroups()[graphGroupStateKey(domain, side, index, type)];
}
function graphCapabilityGroupPortKey(type) {
return `group:${type}`;
}
function participantGroupPortKey(index, type) {
return `${index}:group:${type}`;
}
function endpointIcon(type, graphKey = '', linkKind = '', flow = '') {
const flowKey = flow || type;
const colorClass = flowKey === 'provider-command'
? 'bg-purple-400 border-purple-200 shadow-[0_0_12px_rgba(192,132,252,0.65)]'
: flowKey === 'provider-operation'
? 'bg-purple-300 border-purple-100 shadow-[0_0_12px_rgba(216,180,254,0.65)]'
: flowKey === 'provider-event'
? 'bg-violet-200 border-violet-100 shadow-[0_0_12px_rgba(221,214,254,0.6)]'
: type === 'command'
? 'bg-orange-400 border-orange-200 shadow-[0_0_12px_rgba(251,146,60,0.65)]'
: type === 'operation'
? 'bg-purple-400 border-purple-200 shadow-[0_0_12px_rgba(192,132,252,0.65)]'
: 'bg-blue-400 border-blue-200 shadow-[0_0_12px_rgba(96,165,250,0.65)]';
const shapeClass = type === 'event' ? 'rotate-45 rounded-sm' : 'rounded';
const dataAttr = graphKey ? ` data-graph-${linkKind ? 'participant' : 'capability'}-port="${attr(graphKey)}"` : '';
return ` `;
}
function participantEndpointKind(participant, type, item, shims) {
if (participant && participant._shimOnly) return 'shimmed';
const shimmed = shimmedEndpointsBySource(shims).get(participant && participant.pluginId);
return shimmed && shimmed.has(endpointKey(type, item)) ? 'shimmed' : 'observed';
}
function graphLinkFlow(participant, endpointType) {
if (endpointType === 'command' && hasRole(participant, 'provider')) return 'provider-command';
if (endpointType === 'operation' && hasRole(participant, 'provider')) return 'provider-operation';
if (endpointType === 'event' && hasRole(participant, 'provider')) return 'provider-event';
return endpointType;
}
function graphLinkColor(flow) {
if (flow === 'provider-command') return '#c084fc';
if (flow === 'provider-operation') return '#d8b4fe';
if (flow === 'operation') return '#c084fc';
if (flow === 'provider-event') return '#ddd6fe';
if (flow === 'event') return '#6ea8ff';
return '#fb923c';
}
function graphLinkTitle(link) {
if (link && link.flow === 'provider-command') return 'Provider command link';
if (link && link.flow === 'provider-operation') return 'Provider operation link';
if (link && link.endpointType === 'operation') return 'Operation link';
if (link && link.flow === 'provider-event') return 'Provider event link';
if (link && link.endpointType === 'event') return 'Event link';
return 'Command link';
}
function loadCytoscape() {
if (window.cytoscape) return Promise.resolve(window.cytoscape);
if (cytoscapePromise) return cytoscapePromise;
cytoscapePromise = new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = CYTOSCAPE_URL;
script.async = true;
script.onload = () => window.cytoscape ? resolve(window.cytoscape) : reject(new Error('Cytoscape did not initialize'));
script.onerror = () => reject(new Error('Unable to load Cytoscape'));
(document.head || document.body || document.documentElement).appendChild(script);
});
return cytoscapePromise;
}
function destroyActiveGraph(domain) {
const graph = activeGraphs.get(domain);
if (graph && typeof graph.destroy === 'function') graph.destroy();
activeGraphs.delete(domain);
if (activeGraph === graph) activeGraph = null;
if (!activeGraph && activeGraphs.size) activeGraph = activeGraphs.values().next().value || null;
state.activeGraph = activeGraph;
state.activeGraphs = activeGraphs;
}
function destroyActiveGraphs() {
activeGraphs.forEach(graph => {
if (graph && typeof graph.destroy === 'function') graph.destroy();
});
activeGraphs = new Map();
activeGraph = null;
state.activeGraph = null;
state.activeGraphs = activeGraphs;
}
function rememberActiveGraph(domain, graph) {
destroyActiveGraph(domain);
activeGraphs.set(domain, graph);
activeGraph = graph;
state.activeGraph = graph;
state.activeGraphs = activeGraphs;
}
function graphModel(pipeline, shims, expectedShims, filter = currentGraphFilter()) {
const participants = Array.isArray(pipeline.participants) ? pipeline.participants : [];
const providers = ownerParticipants(participants);
const users = graphParticipants(participants, shims, hasMultiProviderOwner(participants));
const allCommands = uniqueList([...capabilityItems(providers.length ? providers : participants, 'commands'), ...expectedShimCommands(expectedShims), ...observedShimCommands(shims)]);
const allOperations = uniqueList([...capabilityItems(providers.length ? providers : participants, 'operations')]);
const allEvents = uniqueList([...capabilityItems(providers.length ? providers : participants, 'events'), ...expectedShimEvents(expectedShims), ...observedShimEvents(shims)]);
const activeFilter = GRAPH_FILTERS.has(filter) ? filter : 'all';
const { commands, operations, events } = filteredCapabilities(allCommands, allOperations, allEvents, activeFilter);
const linkData = filteredGraphLinks(domainGraphLinks({ commands, operations, events }, users, shims, expectedShims), activeFilter);
const allLinkData = domainGraphLinks({ commands: allCommands, operations: allOperations, events: allEvents }, users, shims, expectedShims);
return { participants, providers, users, allCommands, allOperations, allEvents, commands, operations, events, linkData, allLinkData };
}
function mountDomainGraph(pipeline, shims, expectedShims) {
const ids = graphDomIds(pipeline && pipeline.name);
const container = document.getElementById(ids.cyId);
const frame = document.getElementById(ids.frameId);
if (!container || !frame || !container.getBoundingClientRect || !frame.querySelectorAll) return;
const portAnchor = (element) => {
const frameRect = frame.getBoundingClientRect();
const rect = element.getBoundingClientRect();
const x = rect.left - frameRect.left + (rect.width / 2);
return { x, y: rect.top - frameRect.top + (rect.height / 2) };
};
const portByKey = (selector, datasetKey, key) => Array.from(frame.querySelectorAll(selector)).find(element => element.dataset && element.dataset[datasetKey] === key);
loadCytoscape().then(cytoscape => {
if (!container.isConnected) return;
const schedule = window.requestAnimationFrame ? window.requestAnimationFrame.bind(window) : callback => setTimeout(callback, 0);
schedule(() => {
if (!container.isConnected) return;
const model = graphModel(pipeline, shims, expectedShims);
const width = Math.max(760, Math.round(frame.getBoundingClientRect().width || container.clientWidth || 0));
const height = Math.max(360, Math.round(frame.getBoundingClientRect().height || container.clientHeight || 0));
container.style.width = `${width}px`;
container.style.height = `${height}px`;
container.innerHTML = '';
const elements = [];
for (let index = 0; index < model.linkData.links.length; index += 1) {
const link = model.linkData.links[index];
const sourceKey = isGraphGroupCollapsed(pipeline.name, 'provider', 'all', link.endpointType)
? graphCapabilityGroupPortKey(link.endpointType)
: endpointKey(link.endpointType, link.sourceLabel || link.label);
const targetKey = isGraphGroupCollapsed(pipeline.name, 'participant', link.participantIndex, link.endpointType)
? participantGroupPortKey(link.participantIndex, link.endpointType)
: participantEndpointKey(link.participantIndex, link.endpointType, link.label);
const source = portByKey('[data-graph-capability-port]', 'graphCapabilityPort', sourceKey);
const target = portByKey('[data-graph-participant-port]', 'graphParticipantPort', targetKey);
if (!source || !target) continue;
const sourcePoint = portAnchor(source);
const targetPoint = portAnchor(target);
const sourceId = `link-${index}-source`;
const targetId = `link-${index}-target`;
elements.push({ data: { id: sourceId }, position: sourcePoint });
elements.push({ data: { id: targetId }, position: targetPoint });
elements.push({ data: { id: `link-${index}`, source: sourceId, target: targetId, kind: link.type, endpointType: link.endpointType, flow: link.flow || link.endpointType, participantIndex: link.participantIndex, participantId: link.participantId || '', label: link.label, sourceLabel: link.sourceLabel || link.label } });
}
const graph = cytoscape({
container,
elements,
layout: { name: 'preset', fit: false },
minZoom: 1,
maxZoom: 1,
userZoomingEnabled: false,
userPanningEnabled: false,
boxSelectionEnabled: false,
autoungrabify: true,
style: [
{ selector: 'node', style: { width: 1, height: 1, opacity: 0, label: '' } },
{ selector: 'edge', style: { width: 1.55, 'curve-style': 'unbundled-bezier', 'control-point-distances': '96 -96', 'control-point-weights': '0.35 0.65', 'target-arrow-shape': 'none', opacity: 0.86, 'line-cap': 'round' } },
{ selector: 'edge[endpointType = "command"]', style: { 'line-color': '#fb923c' } },
{ selector: 'edge[flow = "provider-command"]', style: { 'line-color': '#c084fc' } },
{ selector: 'edge[endpointType = "operation"]', style: { 'line-color': '#c084fc' } },
{ selector: 'edge[flow = "provider-operation"]', style: { 'line-color': '#d8b4fe' } },
{ selector: 'edge[endpointType = "event"]', style: { 'line-color': '#6ea8ff' } },
{ selector: 'edge[flow = "provider-event"]', style: { 'line-color': '#ddd6fe' } },
{ selector: 'edge[kind = "shimmed"]', style: { 'line-style': 'dashed', 'line-dash-pattern': [9, 7] } },
{ selector: 'edge.dimmed', style: { opacity: 0.12, width: 1 } },
{ selector: 'edge.highlighted', style: { opacity: 1, width: 3.25, 'z-index': 10 } },
],
});
rememberActiveGraph(pipeline.name, graph);
graph.zoom(1);
graph.pan({ x: 0, y: 0 });
graph.resize();
applyGraphHover(state.graphHover || null);
});
}).catch(() => {
container.innerHTML = 'Graph renderer unavailable. Showing fallback lanes below.
';
});
}
function graphEdgeMatches(edge, hover) {
if (!hover) return false;
const participantIndex = String(edge.data('participantIndex'));
const endpointType = String(edge.data('endpointType') || '');
const label = String(edge.data('label') || '');
const sourceLabel = String(edge.data('sourceLabel') || label);
if (hover.kind === 'participant') return participantIndex === String(hover.participantIndex);
if (hover.kind === 'provider-group') return endpointType === hover.endpointType;
if (hover.kind === 'provider-endpoint') return endpointType === hover.endpointType && sourceLabel === hover.label;
if (hover.kind === 'participant-group') return participantIndex === String(hover.participantIndex) && endpointType === hover.endpointType;
if (hover.kind === 'participant-endpoint') return participantIndex === String(hover.participantIndex) && endpointType === hover.endpointType && label === hover.label;
return false;
}
function graphProviderFocusKey(type, label) {
return `${type || ''}:${label || ''}`;
}
function setGraphFocusStyle(element, mode) {
if (!element || !element.style) return;
if (!mode) {
element.style.opacity = '';
element.style.filter = '';
element.style.fontWeight = '';
element.style.color = '';
return;
}
if (mode === 'active') {
element.style.opacity = '1';
element.style.filter = '';
element.style.fontWeight = '700';
element.style.color = '#f9fafb';
return;
}
element.style.opacity = '0.24';
element.style.filter = 'saturate(0.6)';
element.style.fontWeight = '';
element.style.color = '';
}
function graphFrames() {
if (!document.querySelectorAll) return [];
return Array.from(document.querySelectorAll('[data-domain-graph-frame]'));
}
function frameForDomain(domain) {
const ids = graphDomIds(domain);
return document.getElementById(ids.frameId);
}
function applyProviderHoverStyles(hover, matchingEdges, frame) {
const frames = frame ? [frame] : graphFrames();
if (!frames.length) return;
if (!hover) {
frames.forEach(frameElement => {
if (!frameElement || !frameElement.querySelectorAll) return;
Array.from(frameElement.querySelectorAll('[data-graph-provider-focus]'))
.forEach(target => setGraphFocusStyle(target, null));
});
return;
}
const activeTypes = new Set();
const activeLabels = new Set();
matchingEdges.forEach(edge => {
const type = String(edge.data('endpointType') || '');
const label = String(edge.data('sourceLabel') || edge.data('label') || '');
if (type) activeTypes.add(type);
if (type && label) activeLabels.add(graphProviderFocusKey(type, label));
});
if (hover.kind === 'provider-group' && hover.endpointType) activeTypes.add(hover.endpointType);
if (hover.kind === 'provider-endpoint' && hover.endpointType && hover.label) {
activeTypes.add(hover.endpointType);
activeLabels.add(graphProviderFocusKey(hover.endpointType, hover.label));
}
frames.forEach(frameElement => {
if (!frameElement || !frameElement.querySelectorAll) return;
const targets = Array.from(frameElement.querySelectorAll('[data-graph-provider-focus]'));
targets.forEach(target => {
const type = target.dataset.graphFocusType || '';
const label = target.dataset.graphFocusLabel || '';
const active = label
? activeLabels.has(graphProviderFocusKey(type, label))
: activeTypes.has(type);
setGraphFocusStyle(target, active ? 'active' : 'inactive');
});
});
}
function applyGraphHover(hover) {
state.graphHover = hover || null;
if (!activeGraphs.size) {
applyProviderHoverStyles(null, []);
return;
}
activeGraphs.forEach((graph, domain) => {
if (!graph || typeof graph.edges !== 'function') return;
const frame = frameForDomain(domain);
const edges = graph.edges();
if (!edges || typeof edges.removeClass !== 'function') return;
edges.removeClass('highlighted');
edges.removeClass('dimmed');
if (!hover || hover.domain !== domain) {
applyProviderHoverStyles(null, [], frame);
return;
}
const matchingEdges = [];
edges.forEach(edge => {
const matches = graphEdgeMatches(edge, hover);
if (matches) matchingEdges.push(edge);
edge.addClass(matches ? 'highlighted' : 'dimmed');
});
applyProviderHoverStyles(hover, matchingEdges, frame);
});
}
function graphHoverFromElement(element) {
if (!element || !element.dataset) return null;
const root = element.closest && element.closest('[data-domain-graph]');
const domain = root && root.dataset ? root.dataset.domainGraph : '';
const kind = element.dataset.graphFocusKind;
if (kind) {
return {
domain,
kind,
participantIndex: element.dataset.graphFocusParticipantIndex,
endpointType: element.dataset.graphFocusType || '',
label: element.dataset.graphFocusLabel || '',
};
}
if (element.dataset.graphHoverParticipant != null) {
return { domain, kind: 'participant', participantIndex: element.dataset.graphHoverParticipant };
}
return null;
}
function hasMultiProviderOwner(participants) {
return (Array.isArray(participants) ? participants : []).some(participant => hasRole(participant, 'owner') && participant && (participant.kind === 'provider-coordinator' || participant.ownership === 'multi-provider'));
}
function graphParticipants(participants, shims, includeProviders = false) {
const users = nonCoreParticipants(participants)
.filter(participant => !hasRole(participant, 'owner'))
.filter(participant => includeProviders || !hasRole(participant, 'provider'))
.slice();
const known = new Set(users.map(participant => participant.pluginId));
for (const shim of Array.isArray(shims) ? shims : []) {
const source = String(shim && shim.source || '').trim();
if (!source || source === 'core' || known.has(source)) continue;
users.push({
pluginId: source,
roles: ['participant'],
commands: [],
events: [],
runtime: true,
availability: 'available',
ownership: 'observer-only',
safety: 'safe',
_shimOnly: true,
});
known.add(source);
}
return users;
}
function graphParticipantCount(participants, users) {
const ids = new Set();
for (const participant of [...(Array.isArray(participants) ? participants : []), ...(Array.isArray(users) ? users : [])]) {
const id = String(participant && participant.pluginId || '').trim();
if (id) ids.add(id);
}
return ids.size;
}
function domainIconName(name, review) {
const icon = DOMAIN_ICONS[String(name || '')];
if (icon) return icon;
const lifecycle = review && review.lifecycle;
if (lifecycle === 'future-expansion') return 'history';
if (lifecycle === 'plugin-defined') return 'puzzle';
if (lifecycle === 'diagnostic') return 'fileSearch';
return 'box';
}
function expandedDomains() {
state.expandedDomains = state.expandedDomains || {};
return state.expandedDomains;
}
function isDomainExpanded(name, defaultExpanded) {
const expanded = expandedDomains();
return Object.prototype.hasOwnProperty.call(expanded, name) ? !!expanded[name] : !!defaultExpanded;
}
function panelIdForDomain(name) {
return `capability-domain-${String(name || 'unknown').replace(/[^A-Za-z0-9_-]+/g, '-')}`;
}
function graphDomIds(name) {
const base = `${panelIdForDomain(name)}-graph`;
return {
panelId: `${base}-panel`,
frameId: `${base}-frame`,
cyId: `${base}-cy`,
fallbackId: `${base}-fallback`,
};
}
function domainGroup(name) {
for (let index = 0; index < DOMAIN_GROUPS.length; index += 1) {
const group = DOMAIN_GROUPS[index];
const domainIndex = group.domains.indexOf(name);
if (domainIndex >= 0) return { ...group, groupIndex: index, domainIndex };
}
const fallbackIndex = DOMAIN_GROUPS.findIndex(group => group.id === 'plugin-defined');
const fallback = DOMAIN_GROUPS[fallbackIndex >= 0 ? fallbackIndex : DOMAIN_GROUPS.length - 1];
return { ...fallback, groupIndex: fallbackIndex >= 0 ? fallbackIndex : DOMAIN_GROUPS.length - 1, domainIndex: Number.MAX_SAFE_INTEGER };
}
function sortPipelines(pipelines) {
return (Array.isArray(pipelines) ? pipelines : []).slice().sort((a, b) => {
const ag = domainGroup(a && a.name);
const bg = domainGroup(b && b.name);
if (ag.groupIndex !== bg.groupIndex) return ag.groupIndex - bg.groupIndex;
if (ag.domainIndex !== bg.domainIndex) return ag.domainIndex - bg.domainIndex;
return String(a && a.name || '').localeCompare(String(b && b.name || ''));
});
}
function groupedPipelines(pipelines) {
const byGroup = new Map();
for (const pipeline of sortPipelines(pipelines)) {
const group = domainGroup(pipeline.name);
const entry = byGroup.get(group.id) || { group, pipelines: [] };
entry.pipelines.push(pipeline);
byGroup.set(group.id, entry);
}
return DOMAIN_GROUPS.map(group => byGroup.get(group.id)).filter(Boolean);
}
function participantRow(participant) {
const availability = participant.availability || (participant.enabled ? 'available' : 'disabled');
const availabilityTone = availability === 'available' ? 'clean' : (availability === 'disabled' ? 'muted' : 'warning');
return `
${text(participant.pluginId)}
${pill(availability, availabilityTone)}
${pill(participant.runtime ? 'runtime' : 'manifest', participant.runtime ? 'info' : 'muted')}
${participant.incompatible ? pill('incompatible', 'conflict') : ''}
${chips(participant.roles, 'bg-dark-700 text-gray-300 border border-gray-800')}
Commands: ${text((participant.commands || []).join(', ') || 'none')}
Operations: ${text((participant.operations || []).join(', ') || 'none')}
Requests: ${text((participant.requests || []).join(', ') || 'none')}
Observes: ${text((participant.observes || []).join(', ') || 'none')}
Safety: ${text(participant.safety || 'safe')} ${participant.kind ? `/ Kind: ${text(participant.kind)}` : `/ Ownership: ${text(participant.ownership || 'exclusive-owner')}`}
`;
}
function participantLabel(participant) {
const roles = Array.isArray(participant && participant.roles) ? participant.roles : [];
return `${participant && participant.pluginId ? participant.pluginId : 'unknown'}${roles.length ? ` (${roles.join(', ')})` : ''}`;
}
function participantChips(participants, emptyLabel, tone = 'muted') {
const list = Array.isArray(participants) ? participants : [];
if (!list.length) return `${text(emptyLabel)} `;
const className = tone === 'observed'
? 'rounded border border-teal-700/60 bg-teal-500/10 px-2 py-1 text-xs text-teal-300'
: 'rounded border border-gray-800 bg-dark-700 px-2 py-1 text-xs text-gray-300';
return list.map(participant => `${text(participantLabel(participant))} `).join('');
}
function capabilityRelationshipRows(pipelines) {
return sortPipelines(pipelines).map(pipeline => {
const participants = Array.isArray(pipeline && pipeline.participants) ? pipeline.participants : [];
return {
capability: pipeline.name,
providers: ownerProviderParticipants(participants),
users: nonCoreParticipants(participants),
};
});
}
function capabilityRelationshipMap(pipelines) {
const rows = capabilityRelationshipRows(pipelines);
if (!rows.length) return '';
return `
Capability relationship map
Owner/provider capabilities connect to non-core participants using the same capability.
${pill(`${rows.reduce((sum, row) => sum + row.users.length, 0)} external`, rows.some(row => row.users.length) ? 'observed' : 'muted')}
Owner/provider
Capability
Non-core usage
${rows.map(row => `
${participantChips(row.providers, 'No owner/provider')}
${iconSvg('list')}${text(row.capability)}
${participantChips(row.users, 'No usage', row.users.length ? 'observed' : 'muted')}
`).join('')}
`;
}
function shimTitle(shim) {
const surface = String(shim && shim.legacySurface || '');
if (surface === 'register_library_provider') return 'Legacy library provider detected';
if (surface === 'refresh') return 'Legacy library refresh detected';
if (surface === 'select') return 'Legacy library provider selection detected';
if (surface === 'sync-song') return 'Legacy library song sync detected';
return 'Legacy compatibility surface detected';
}
function formatTime(value) {
if (!value) return '';
const date = new Date(value);
if (Number.isNaN(date.getTime())) return String(value);
return `${date.toLocaleDateString()} ${date.toLocaleTimeString()}`;
}
function matchingExpectedShim(shim, expectedShims) {
const surface = String(shim && shim.legacySurface || '');
return (Array.isArray(expectedShims) ? expectedShims : []).find(expected => {
const expectedSurface = String(expected && expected.legacySurface || '');
if (!expectedSurface) return false;
return expectedSurface.endsWith('*') ? surface.startsWith(expectedSurface.slice(0, -1)) : surface === expectedSurface;
}) || null;
}
function shimRow(shim, expectedShims) {
const hitCount = Number(shim.hitCount || 0);
const expected = matchingExpectedShim(shim, expectedShims);
const status = hitCount > 0 ? pill('Used', 'used') : pill(shim.status || 'active', shim.status === 'active' ? 'info' : 'muted');
const lastSeen = formatTime(shim.lastHitAt);
return `
${text(shimTitle(shim))}
${text(shim.legacySurface || 'legacy')}
${copyButton(shim.legacySurface)}
${status}
${hitCount} hit${hitCount === 1 ? '' : 's'}${lastSeen ? ` / last seen ${text(lastSeen)}` : ''}
Source: ${text(shim.source || 'unknown')}${shim.providerId ? ` / Provider: ${text(shim.providerId)}` : ''}
${text(shim.reason || expected?.reason || `Maps to ${shim.capability || 'unknown'} compatibility bridge`)}
`;
}
function shimSection(shims, expectedShims) {
const list = Array.isArray(shims) ? shims : [];
const hits = totalShimHits(list);
return `
Compatibility shims
${list.length} shim${list.length === 1 ? '' : 's'} / ${hits} hit${hits === 1 ? '' : 's'}
${list.length ? `
${list.map(shim => shimRow(shim, expectedShims)).join('')}
` : '
No compatibility shims observed for this domain.
'}
`;
}
function expectedShimObserved(expected, shims) {
const surface = String(expected && expected.legacySurface || '');
if (!surface) return false;
const prefix = surface.endsWith('*') ? surface.slice(0, -1) : '';
return (Array.isArray(shims) ? shims : []).some(shim => {
const observed = String(shim && shim.legacySurface || '');
return prefix ? observed.startsWith(prefix) : observed === surface;
});
}
function expectedShimCell(expected, observed) {
if (!expected) return 'Not expected ';
return `
${pill(observed ? 'Observed' : 'Not observed', observed ? 'observed' : 'muted')}
${copyButton(expected.legacySurface)}
${text(expected.legacySurface || 'legacy')}
${text(expected.reason || '')}
`;
}
function expectedShimGroup(expectedShims, observedShims) {
const groups = new Map();
for (const expected of Array.isArray(expectedShims) ? expectedShims : []) {
const surface = String(expected && expected.legacySurface || '');
const eventMatch = surface.match(/^window\.feedBack\.(emit|on):(.+)$/);
const key = eventMatch ? eventMatch[2] : surface;
const type = eventMatch ? (eventMatch[1] === 'emit' ? 'emit' : 'listener') : 'surface';
const entry = groups.get(key) || { group: key, emit: null, listener: null, surfaces: [] };
const withState = { ...expected, observed: expectedShimObserved(expected, observedShims) };
if (type === 'emit') entry.emit = withState;
else if (type === 'listener') entry.listener = withState;
else entry.surfaces.push(withState);
groups.set(key, entry);
}
return Array.from(groups.values());
}
function expectedSurfaceRow(group) {
return `
${text(group.group)}
${expectedShimCell(group.emit, group.emit && group.emit.observed)}
${expectedShimCell(group.listener, group.listener && group.listener.observed)}
${group.surfaces.length ? group.surfaces.map(item => expectedShimCell(item, item.observed)).join('
') : 'None '}
`;
}
function expectedShimSection(domain, expectedShims, observedShims) {
const list = Array.isArray(expectedShims) ? expectedShims : [];
if (!list.length) {
return `
Expected legacy surfaces
No legacy compatibility shims are expected for this domain.
`;
}
const filter = (state.surfaceFilters && state.surfaceFilters[domain]) || 'all';
const groups = expectedShimGroup(list, observedShims).filter(group => {
const entries = [group.emit, group.listener, ...group.surfaces].filter(Boolean);
if (filter === 'observed') return entries.some(entry => entry.observed);
if (filter === 'problems') return entries.some(entry => !entry.observed);
return true;
});
return `
Expected legacy surfaces
${list.length} expected / grouped by event or API surface
Show all
Show observed
Show not observed
Group
Emit surface
Listener surface
Other surface
${groups.map(expectedSurfaceRow).join('') || 'No surfaces match this filter. '}
`;
}
function participantSection(pipeline) {
const participants = Array.isArray(pipeline.participants) ? pipeline.participants : [];
const owners = coreOwners(participants);
const extras = nonCoreParticipants(participants);
return `
Capability usage
Core ownership separated from non-core participants.
${pill(`${extras.length} non-core`, extras.length ? 'observed' : 'muted')}
Core owner
${owners.map(participantRow).join('') || '
No core owner is registered for this domain.
'}
Non-core participants
${extras.map(participantRow).join('') || '
No participants are using this capability.
'}
`;
}
function participantUsesByLinkData(users, linkData) {
const uses = (Array.isArray(users) ? users : []).map(() => ({ commands: [], operations: [], events: [] }));
for (const link of Array.isArray(linkData && linkData.links) ? linkData.links : []) {
const entry = uses[link.participantIndex];
if (!entry) continue;
const list = link.endpointType === 'operation' ? entry.operations : (link.endpointType === 'command' ? entry.commands : entry.events);
if (!list.includes(link.label)) list.push(link.label);
}
return uses;
}
function visibleGraphParticipants(users, usesByIndex) {
return (Array.isArray(users) ? users : []).map((participant, index) => {
const uses = (Array.isArray(usesByIndex) && usesByIndex[index]) || { commands: [], operations: [], events: [] };
return { participant, index, uses };
}).filter(entry => entry.uses.commands.length || entry.uses.operations.length || entry.uses.events.length);
}
function graphHeaderSummary(model) {
const summaryUses = participantUsesByLinkData(model.users, model.allLinkData);
const summaryParticipants = visibleGraphParticipants(model.users, summaryUses);
const shimmed = model.allLinkData.links.filter(link => link.type === 'shimmed').length;
const observed = model.allLinkData.links.filter(link => link.type === 'observed').length;
return {
participantTotal: graphParticipantCount([], summaryParticipants.map(entry => entry.participant)),
capabilityTotal: model.allCommands.length + model.allOperations.length + model.allEvents.length,
observed,
shimmed,
};
}
function graphGroupButton(domain, side, index, title, type, count, collapsed, flow = '') {
const key = graphGroupStateKey(domain, side, index, type);
const isProvider = side === 'provider';
const port = collapsed
? (isProvider
? `${endpointIcon(type, graphCapabilityGroupPortKey(type))} `
: `${endpointIcon(type, participantGroupPortKey(index, type), 'group', flow)} `)
: '';
const align = isProvider ? 'justify-end text-right' : 'justify-start text-left';
const countPill = pill(String(count), 'muted');
const focusAttrs = isProvider
? ` data-graph-focus-kind="provider-group" data-graph-focus-type="${attr(type)}" data-graph-provider-focus="group"`
: ` data-graph-focus-kind="participant-group" data-graph-focus-participant-index="${attr(index)}" data-graph-focus-type="${attr(type)}"`;
return `
${isProvider ? `${countPill}${text(title)} ` : `${port}${text(title)} ${countPill}`}
${isProvider ? port : ''}
`;
}
function providerCapabilityGroup(domain, title, items, type) {
const collapsed = isGraphGroupCollapsed(domain, 'provider', 'all', type);
return `
${graphGroupButton(domain, 'provider', 'all', title, type, items.length, collapsed)}
${collapsed ? '' : `
${items.map(item => `
${text(item)} ${endpointIcon(type, endpointKey(type, item))}
`).join('') || '
None declared
'}
`}
`;
}
function ownerDescription(domain, participant, review) {
const explicit = String(participant && (participant.description || participant.summary) || '').replace(/\s+/g, ' ').trim();
if (explicit) return explicit;
const participantId = participant && participant.pluginId ? participant.pluginId : '';
if (!participantId || participantId === 'No owner/provider') return `No owner is currently registered for the ${domain} domain.`;
if (participantId === 'core' && review && review.summary) return review.summary;
if (hasRole(participant, 'owner')) return `Owns the ${domain} capability contract and exposes its provider endpoints.`;
if (hasRole(participant, 'provider')) return `Provides endpoints for the ${domain} capability domain.`;
return `Participates in the ${domain} capability domain.`;
}
function providerPanel(domain, participants, commands, operations, events, review) {
const ownerEntries = participants.length ? participants : [{ pluginId: 'No owner/provider', roles: [] }];
return `
${ownerEntries.map(participant => `
${text(participant.pluginId || 'unknown')}
${ownerHeaderIcons(participant)}
`).join('')}
${operations.length ? providerCapabilityGroup(domain, 'Operations', operations, 'operation') : ''}
${commands.length ? providerCapabilityGroup(domain, 'Commands', commands, 'command') : ''}
${events.length ? providerCapabilityGroup(domain, 'Events', events, 'event') : ''}
${commands.length || operations.length || events.length ? '' : '
No endpoints declared.
'}
${ownerEntries.map(participant => `
${text(ownerDescription(domain, participant, review))}
`).join('')}
`;
}
function participantEndpointGroup(domain, participant, uses, index, shims, title, type) {
const items = type === 'operation' ? uses.operations : (type === 'command' ? uses.commands : uses.events);
if (!items.length) return '';
const collapsed = isGraphGroupCollapsed(domain, 'participant', index, type);
const flow = graphLinkFlow(participant, type);
return `
${graphGroupButton(domain, 'participant', index, title, type, items.length, collapsed, flow)}
${collapsed ? '' : `
${items.map(item => `
${endpointIcon(type, participantEndpointKey(index, type, item), participantEndpointKind(participant, type, item, shims), flow)} ${text(item)}
`).join('')}
`}
`;
}
function participantGraphCard(domain, participant, uses, index, shims) {
const groupMarkup = [
participantEndpointGroup(domain, participant, uses, index, shims, 'Operations', 'operation'),
participantEndpointGroup(domain, participant, uses, index, shims, 'Commands', 'command'),
participantEndpointGroup(domain, participant, uses, index, shims, 'Events', 'event'),
].filter(Boolean).join('');
const primaryRoleIcon = providerRoleIcon(participant);
return `
${primaryRoleIcon}
${text(participant.pluginId)}
${participantOriginIcon(participant)}${availabilityBadge(participant.availability)}
${groupMarkup || '
No command, operation, or event usage declared.
'}
`;
}
function domainGraphLinks(capabilities, participants, shims, expectedShims) {
const commandOffset = capabilities.operations.length;
const eventOffset = capabilities.operations.length + capabilities.commands.length;
const operationIndex = new Map(capabilities.operations.map((item, index) => [item, index]));
const commandIndex = new Map(capabilities.commands.map((item, index) => [item, commandOffset + index]));
const eventIndex = new Map(capabilities.events.map((item, index) => [item, eventOffset + index]));
const nodeCount = Math.max(1, capabilities.commands.length + capabilities.operations.length + capabilities.events.length);
const participantCount = Math.max(1, participants.length);
const shimmedBySource = shimmedEndpointsBySource(shims);
const links = [];
participants.forEach((participant, participantIndex) => {
const uses = participantUses(participant, shims, expectedShims);
const shimmedEndpoints = shimmedBySource.get(participant && participant.pluginId) || new Set();
for (const item of uses.commands) {
const linkType = participant && (participant._shimOnly || shimmedEndpoints.has(endpointKey('command', item))) ? 'shimmed' : 'observed';
if (commandIndex.has(item)) links.push({ type: linkType, flow: graphLinkFlow(participant, 'command'), capabilityIndex: commandIndex.get(item), participantIndex, participantId: participant && participant.pluginId || '', label: item, sourceLabel: item, endpointType: 'command' });
}
for (const item of uses.operations) {
const linkType = participant && (participant._shimOnly || shimmedEndpoints.has(endpointKey('operation', item))) ? 'shimmed' : 'observed';
if (operationIndex.has(item)) links.push({ type: linkType, flow: graphLinkFlow(participant, 'operation'), capabilityIndex: operationIndex.get(item), participantIndex, participantId: participant && participant.pluginId || '', label: item, sourceLabel: item, endpointType: 'operation' });
}
for (const item of uses.events) {
const exact = eventIndex.has(item) ? item : Array.from(eventIndex.keys()).find(event => event.endsWith('*') && item.startsWith(event.slice(0, -1)));
const linkType = participant && (participant._shimOnly || shimmedEndpoints.has(endpointKey('event', item))) ? 'shimmed' : 'observed';
if (exact) links.push({ type: linkType, flow: graphLinkFlow(participant, 'event'), capabilityIndex: eventIndex.get(exact), participantIndex, participantId: participant && participant.pluginId || '', label: item, sourceLabel: exact, endpointType: 'event' });
}
});
return { links, nodeCount, participantCount };
}
function domainGraphSvg(linkData) {
const yForCapability = index => 12 + (index + 0.5) * (76 / linkData.nodeCount);
const yForParticipant = index => 14 + (index + 0.5) * (72 / linkData.participantCount);
const paths = linkData.links.map(link => {
const y1 = yForCapability(link.capabilityIndex);
const y2 = yForParticipant(link.participantIndex);
const color = graphLinkColor(link.flow || link.endpointType);
const dash = link.type === 'shimmed' ? ' stroke-dasharray="4 4"' : '';
return `${text(graphLinkTitle(link))}: ${text(link.label)} `;
}).join('');
return `${paths} `;
}
function domainGraphView(pipeline, shims, expectedShims, options = {}) {
const model = graphModel(pipeline, shims, expectedShims);
const { providers, users, commands, operations, events, linkData } = model;
const visibleUses = participantUsesByLinkData(users, linkData);
const visibleParticipants = visibleGraphParticipants(users, visibleUses);
const { participantTotal, capabilityTotal, observed, shimmed } = graphHeaderSummary(model);
const status = compatibilityState(pipeline.conflicts || []);
const review = reviewInfo(pipeline);
const linkMetadata = linkData.links.map(link => `${text(link.label)} `).join('');
const collapsible = !!options.collapsible;
const expanded = collapsible ? isDomainExpanded(pipeline.name, !!options.defaultExpanded) : true;
const ids = graphDomIds(pipeline.name);
const actionLabel = `${expanded ? 'Collapse' : 'Expand'} ${pipeline.name} domain graph`;
const statusIcon = status.tone === 'conflict' ? 'alert' : (status.tone === 'warning' ? 'shieldAlert' : 'shield');
const domainIcon = domainIconName(pipeline.name, review);
const titleMarkup = collapsible
? `${text(pipeline.name)} ${iconSvg(expanded ? 'chevronDown' : 'chevronRight', 'h-4 w-4')} `
: `${text(pipeline.name)} `;
return `
${iconSvg(domainIcon, 'h-5 w-5')}
${titleMarkup}
${compactIconBadge('users', participantTotal, `${participantTotal} participant${participantTotal === 1 ? '' : 's'}`, 'muted')}
${compactIconBadge('box', capabilityTotal, `${capabilityTotal} capability endpoint${capabilityTotal === 1 ? '' : 's'}`, 'muted')}
${compactIconBadge('eye', observed, `${observed} observed link${observed === 1 ? '' : 's'}`, observed ? 'observed' : 'muted')}
${compactIconBadge('plug', shimmed, `${shimmed} shimmed link${shimmed === 1 ? '' : 's'}`, shimmed ? 'used' : 'muted')}
${compactIconBadge(statusIcon, '', `Domain status: ${status.label}`, status.tone)}
${expanded ? `
${linkMetadata}
${providerPanel(pipeline.name, providers, commands, operations, events, review)}
${visibleParticipants.map(entry => participantGraphCard(pipeline.name, entry.participant, entry.uses, entry.index, shims)).join('') || '
No participants are using this capability.
'}
Show: ${graphFilterButton('all', 'All')}${graphFilterButton('operations', 'Operations')}${graphFilterButton('commands', 'Commands')}${graphFilterButton('events', 'Events')}${graphFilterButton('shimmed', 'Shimmed')}
Legend
${OPERATION_ICON}Operation
${COMMAND_ICON}Command
${EVENT_ICON}Event
Operation link
${PROVIDER_OPERATION_LINK}Provider operation link
Command link
${PROVIDER_COMMAND_LINK}Provider command link
Event link
${PROVIDER_EVENT_LINK}Provider event link
Shimmed link
` : ''}
`;
}
function pipelineCard(pipeline, shimsByCapability, expectedShimsByCapability, options = {}) {
const shims = (shimsByCapability && shimsByCapability.get(pipeline.name)) || [];
const expectedShims = (expectedShimsByCapability && expectedShimsByCapability.get(pipeline.name)) || [];
return domainGraphView(pipeline, shims, expectedShims, { collapsible: true, defaultExpanded: !!options.defaultExpanded });
}
function pipelineGroupSection(entry, shimsByCapability, expectedShimsByCapability, options = {}) {
const pipelines = entry.pipelines || [];
const domainCount = pipelines.length;
return `
${text(entry.group.label)}
${text(entry.group.summary)}
${pill(`${domainCount} domain${domainCount === 1 ? '' : 's'}`, 'muted')}
${pipelines.map(pipeline => pipelineCard(pipeline, shimsByCapability, expectedShimsByCapability, options)).join('')}
`;
}
function summaryDashboard(data, pipelines, compatibilityShims) {
const participants = Array.isArray(data.participants) ? data.participants : [];
const conflicts = pipelines.reduce((sum, pipeline) => sum + ((pipeline.conflicts || []).length), 0);
const warnings = (Array.isArray(data.missingProviders) ? data.missingProviders.length : 0)
+ (Array.isArray(data.unsupportedVersions) ? data.unsupportedVersions.length : 0);
const shimHits = totalShimHits(compatibilityShims);
const status = conflicts ? { label: 'Conflicts', tone: 'conflict' } : (warnings ? { label: 'Warnings', tone: 'warning' } : { label: 'Clean', tone: 'clean' });
return `
${metricCard('Domains', pipelines.length, 'muted')}
${metricCard('Participants', participants.length, 'muted')}
${metricCard(shimHits === 1 ? 'Shim hit' : 'Shim hits', shimHits, shimHits > 0 ? 'used' : 'muted')}
Status
${text(status.label)}
`;
}
function audioSessionSnapshot() {
const api = window.feedBack && window.feedBack.audioSession;
if (!api || typeof api.snapshot !== 'function') return null;
try { return api.snapshot(); }
catch (_) { return null; }
}
function playbackSnapshot() {
const api = window.feedBack && window.feedBack.playback;
if (!api || typeof api.snapshot !== 'function') return null;
try { return api.snapshot({ exportMode: 'local-inspector' }); }
catch (_) { return null; }
}
function playbackSupportPanel(playbackData) {
if (!playbackData || !playbackData.state) return '';
const state = playbackData.state || {};
const session = state.sessionId || 'unknown';
const target = state.target || {};
const display = target.localDisplay || {};
const media = state.media || {};
const route = state.route || media.route || {};
const loop = state.loop || media.loop || {};
const participants = Array.isArray(playbackData.participants) ? playbackData.participants : [];
const bridges = Array.isArray(playbackData.bridges) ? playbackData.bridges : [];
const recentOutcomes = playbackData.history && playbackData.history.current && Array.isArray(playbackData.history.current.recentOutcomes)
? playbackData.history.current.recentOutcomes.slice(-6) : [];
const recentEvents = playbackData.history && playbackData.history.current && Array.isArray(playbackData.history.current.lifecycleEvents)
? playbackData.history.current.lifecycleEvents.slice(-6) : [];
const bridgeHits = bridges.reduce((sum, bridge) => sum + Number(bridge.hitCount || 0), 0);
const label = [display.title, display.artist].filter(Boolean).join(' - ') || target.targetId || 'none';
const timeLabel = media.currentTime == null ? 'unknown' : `${Number(media.currentTime).toFixed(2)}s`;
const durationLabel = media.duration == null ? 'unknown' : `${Number(media.duration).toFixed(2)}s`;
return `
Playback
Transport session, route, loop, requester, bridge, and recent outcome state from the playback host.
${pill(`state: ${state.state || 'unknown'}`, state.state === 'playing' || state.state === 'ready' ? 'clean' : (state.state === 'failed' || state.state === 'unavailable' ? 'conflict' : 'muted'))}
${pill(`route: ${route.routeKind || 'unknown'}`, route.state === 'active' ? 'clean' : (route.state === 'degraded' ? 'warning' : 'muted'))}
${pill(loop && loop.enabled ? 'loop: active' : 'loop: inactive', loop && loop.enabled ? 'used' : 'muted')}
${pill(`${participants.length} participant${participants.length === 1 ? '' : 's'}`, participants.length ? 'info' : 'muted')}
${pill(`${bridgeHits} bridge hit${bridgeHits === 1 ? '' : 's'}`, bridgeHits ? 'used' : 'muted')}
Session: ${text(session)}
Target: ${text(label)}
Time: ${text(timeLabel)} / ${text(durationLabel)}
Loop: ${loop && loop.enabled ? `${text(loop.startTime)}-${text(loop.endTime)} (${text(loop.state || 'active')})` : 'none'}
Route: ${text(route.routeKind || 'unknown')} (${text(route.state || 'unknown')})${route.safeReason ? ` - ${text(route.safeReason)}` : ''}
Participants: ${participants.map(participant => text(participant.requesterId || participant.observerId || participant.role || 'unknown')).join(', ') || 'none'}
Bridges: ${bridges.map(bridge => `${text(bridge.bridgeId)}:${text(bridge.hitCount || 0)}`).join(', ') || 'none'}
Outcomes: ${recentOutcomes.map(outcome => `${text(outcome.operation)}:${text(outcome.status || outcome.outcome)}`).join(', ') || 'none'}
Events: ${recentEvents.map(event => `${text(event.event)}:${text(event.state)}`).join(', ') || 'none'}
`;
}
function audioDomainSupportPanel(audioData) {
if (!audioData || !audioData.domains) return '';
const mix = audioData.domains['audio-mix'] || {};
const route = mix.route || (audioData.session && audioData.session.route) || {};
const analyser = mix.analyser || (audioData.session && audioData.session.analyser) || {};
const input = audioData.domains['audio-input'] || {};
const monitoring = audioData.domains['audio-monitoring'] || {};
const stems = audioData.domains['stems'] || {};
const participants = Array.isArray(mix.participants) ? mix.participants : [];
const sources = Array.isArray(input.sources) ? input.sources : [];
const selectedInput = input.selected || null;
const openInputSessions = Array.isArray(input.openSessions) ? input.openSessions : [];
const monitoringProviders = Array.isArray(monitoring.providers) ? monitoring.providers : [];
const selectedMonitoringProvider = monitoring.selectedProvider || null;
const monitoringSessions = Array.isArray(monitoring.sessions) ? monitoring.sessions : [];
const monitoringBridges = Array.isArray(monitoring.bridges) ? monitoring.bridges : [];
const inputBridges = Array.isArray(input.bridges) ? input.bridges : [];
const bridges = [
...(Array.isArray(mix.bridges) ? mix.bridges : []),
...inputBridges,
...monitoringBridges,
...(Array.isArray(stems.bridges) ? stems.bridges : []),
];
const bridgeHits = bridges.reduce((sum, bridge) => sum + Number(bridge.hitCount || 0), 0);
const faders = Array.isArray(mix.faders) ? mix.faders : [];
const failedOutcomes = (Array.isArray(audioData.recentOutcomes) ? audioData.recentOutcomes : [])
.filter(outcome => outcome && (outcome.domain === 'audio-mix' || outcome.domain === 'audio-input' || outcome.domain === 'audio-monitoring') && (outcome.outcome === 'failed' || outcome.outcome === 'denied' || outcome.outcome === 'degraded' || outcome.outcome === 'unavailable' || outcome.outcome === 'provider-selection-required' || outcome.status === 'timeout'))
.slice(-5);
const claims = Array.isArray(stems.claims) ? stems.claims : [];
const owner = stems.owner || null;
return `
Audio session
Route, mix participants, stem owner, claims, and bridge hits from the active session host.
${pill(`route: ${route.routeKind || 'unknown'}`, route.availability === 'available' ? 'clean' : 'warning')}
${pill(`${participants.length} mix participant${participants.length === 1 ? '' : 's'}`, participants.length ? 'info' : 'muted')}
${pill(`${faders.length} fader${faders.length === 1 ? '' : 's'}`, faders.length ? 'info' : 'muted')}
${pill(`${input.totalSources ?? sources.length} input source${(input.totalSources ?? sources.length) === 1 ? '' : 's'}`, sources.length ? 'info' : 'muted')}
${pill(`${input.totalOpenSessions ?? openInputSessions.length} open input${(input.totalOpenSessions ?? openInputSessions.length) === 1 ? '' : 's'}`, openInputSessions.length ? 'used' : 'muted')}
${pill(`${monitoring.totalProviders ?? monitoringProviders.length} monitor provider${(monitoring.totalProviders ?? monitoringProviders.length) === 1 ? '' : 's'}`, monitoringProviders.length ? 'info' : 'muted')}
${pill(`${monitoring.totalSessions ?? monitoringSessions.length} monitor${(monitoring.totalSessions ?? monitoringSessions.length) === 1 ? '' : 's'}`, monitoringSessions.length ? 'info' : 'muted')}
${pill(owner ? `stems: ${owner.ownerId}` : 'stems: no owner', owner ? 'clean' : 'muted')}
${pill(`${claims.length} claim${claims.length === 1 ? '' : 's'}`, claims.length ? 'used' : 'muted')}
${pill(`${bridgeHits} bridge hit${bridgeHits === 1 ? '' : 's'}`, bridges.length ? 'used' : 'muted')}
Route: ${text(route.routeKind || 'unknown')} (${text(route.availability || 'unknown')})
Analyser: ${text(analyser.source || 'unavailable')} (${text(analyser.availability || 'unavailable')})
Faders: ${faders.map(fader => `${text(fader.label || fader.faderLabel || fader.participantId)}:${text(fader.availability || 'unknown')}:${text(fader.sourceMode || 'native')}${fader.lastRejectedValue != null ? ':failed' : ''}`).join(', ') || 'none'}
Input: ${sources.map(s => `${text(s.label || s.sourceId || s.kind)}:${text(s.availability || 'unknown')}:${text(s.channelSummary && s.channelSummary.channelShape ? s.channelSummary.channelShape : s.channelShape || 'unknown')}:${text(s.sourceMode || 'native')}${s.supersededBy ? ':superseded' : ''}`).join(', ') || 'none'}
Selected input: ${selectedInput ? `${text(selectedInput.logicalSourceKey || selectedInput.sourceId || 'selected')}:${text(selectedInput.availability || 'unknown')}:${text(selectedInput.restoreStatus || 'unknown')}` : 'none'}
Open input: ${openInputSessions.map(s => `${text(s.openSessionId)}:${text(s.channelShape || 'unknown')}:${text(s.state || 'unknown')}:${text((s.requesters || []).map(r => r.requesterId).join('+'))}`).join(', ') || 'none'}
Monitoring providers: ${monitoringProviders.map(p => `${text(p.label || p.providerId)}:${text(p.availability || 'unknown')}:${text(p.sourceMode || 'native')}${p.supersededBy ? ':superseded' : ''}`).join(', ') || 'none'}
Selected monitoring: ${selectedMonitoringProvider ? `${text(selectedMonitoringProvider.logicalMonitoringKey || selectedMonitoringProvider.providerId || 'selected')}:${text(selectedMonitoringProvider.availability || 'unknown')}:${text(selectedMonitoringProvider.providerId || 'unknown')}` : 'none'}
Monitoring: ${monitoringSessions.map(s => `${text(s.monitoringId)}:${text(s.state)}:${text((s.requesters || []).map(r => r.requesterId).join('+'))}:${text((s.sourceRef && (s.sourceRef.logicalSourceKey || s.sourceRef.sourceId)) || 'unknown')}`).join(', ') || 'none'}
Direct monitor: ${monitoring.directMonitor ? `${text(monitoring.directMonitor.state || monitoring.directMonitor.preference || 'unknown')}:${text(monitoring.directMonitor.control || 'unknown')}:${text(monitoring.directMonitor.applied === true ? 'applied' : (monitoring.directMonitor.applied === false ? 'not-applied' : 'unknown'))}` : 'unknown'}
Stem owner: ${text(owner && owner.ownerId ? owner.ownerId : 'none')}
Participants: ${participants.map(p => text(p.label || p.participantId)).join(', ') || 'none'}
Claims: ${claims.map(c => `${text(c.claimId)}:${text(c.state)}`).join(', ') || 'none'}
Bridges: ${bridges.map(b => `${text(b.bridgeId)}:${text(b.outcome || 'handled')}${b.reason ? ` (${text(b.reason)})` : ''}`).join(', ') || 'none'}
Input bridges: ${inputBridges.map(b => `${text(b.bridgeId)}:${text(b.status || b.outcome || 'handled')}`).join(', ') || 'none'}
Monitoring bridges: ${monitoringBridges.map(b => `${text(b.bridgeId)}:${text(b.status || b.outcome || 'handled')}`).join(', ') || 'none'}
Failures: ${failedOutcomes.map(outcome => `${text(outcome.domain)}:${text(outcome.operation)}:${text(outcome.faderId || outcome.monitoringId || outcome.sourceId || outcome.logicalSourceKey || outcome.participantId || outcome.requesterId)}:${text(outcome.status || outcome.outcome)}`).join(', ') || 'none'}
`;
}
function syncFilterOptions(filter, pipelines) {
if (!filter) return '';
const names = sortPipelines(pipelines).map(pipeline => pipeline && pipeline.name).filter(Boolean);
const current = names.includes(filter.value) ? filter.value : '';
const options = groupedPipelines(pipelines).map(entry => (
`${entry.pipelines.map(pipeline => `${text(pipeline.name)} `).join('')} `
)).join('');
filter.innerHTML = `All domains ${options}`;
filter.value = current;
return current;
}
function render() {
const filter = document.getElementById('capability-inspector-filter');
const content = document.getElementById('capability-inspector-content');
const empty = document.getElementById('capability-inspector-empty');
const summary = document.getElementById('capability-inspector-summary');
if (!content || !empty) return;
const data = snapshot();
if (!data || data.error) {
content.innerHTML = '';
empty.classList.remove('hidden');
empty.textContent = data && data.error ? data.error : 'Capability runtime is loading...';
if (summary) summary.textContent = '';
return;
}
empty.classList.add('hidden');
const pipelines = sortPipelines((Array.isArray(data.pipelines) ? data.pipelines : [])
.filter(pipeline => pipeline && !DOCUMENTATION_ONLY_DOMAINS.has(pipeline.name)));
const selected = syncFilterOptions(filter, pipelines);
const visible = selected ? pipelines.filter(pipeline => pipeline.name === selected) : pipelines;
const shimsByCapability = new Map();
for (const shim of Array.isArray(data.compatibilityShims) ? data.compatibilityShims : []) {
const capability = shim && shim.capability;
if (!capability) continue;
const list = shimsByCapability.get(capability) || [];
list.push(shim);
shimsByCapability.set(capability, list);
}
const expectedShimsByCapability = new Map();
for (const shim of Array.isArray(data.expectedCompatibilityShims) ? data.expectedCompatibilityShims : []) {
const capability = shim && shim.capability;
if (!capability) continue;
const list = expectedShimsByCapability.get(capability) || [];
list.push(shim);
expectedShimsByCapability.set(capability, list);
}
const compatibilityShims = Array.isArray(data.compatibilityShims) ? data.compatibilityShims : [];
if (summary) summary.innerHTML = summaryDashboard(data, pipelines, compatibilityShims);
destroyActiveGraphs();
const playbackPanel = playbackSupportPanel(playbackSnapshot());
const audioPanel = audioDomainSupportPanel(audioSessionSnapshot());
content.innerHTML = playbackPanel + audioPanel + (selected
? visible.map(pipeline => domainGraphView(pipeline, shimsByCapability.get(pipeline.name) || [], expectedShimsByCapability.get(pipeline.name) || [])).join('')
: groupedPipelines(visible).map(entry => pipelineGroupSection(entry, shimsByCapability, expectedShimsByCapability, { defaultExpanded: false })).join(''))
|| 'No capability domains registered.
';
const graphPipelines = selected ? visible : visible.filter(pipeline => isDomainExpanded(pipeline.name, false));
graphPipelines.forEach(pipeline => {
mountDomainGraph(pipeline, shimsByCapability.get(pipeline.name) || [], expectedShimsByCapability.get(pipeline.name) || []);
});
}
function scheduleRender() {
if (renderScheduled) return;
renderScheduled = true;
const schedule = window.requestAnimationFrame ? window.requestAnimationFrame.bind(window) : callback => setTimeout(callback, 0);
schedule(() => {
renderScheduled = false;
render();
});
}
function install() {
const refresh = document.getElementById('capability-inspector-refresh');
const filter = document.getElementById('capability-inspector-filter');
if (refresh && !refresh.dataset.bound) {
refresh.dataset.bound = '1';
refresh.addEventListener('click', render);
}
if (filter && !filter.dataset.bound) {
filter.dataset.bound = '1';
filter.addEventListener('change', render);
}
const content = document.getElementById('capability-inspector-content');
if (content && !content.dataset.bound) {
content.dataset.bound = '1';
content.addEventListener('change', (event) => {
const target = event.target;
if (!target || !target.dataset || !target.dataset.surfaceFilter) return;
state.surfaceFilters = state.surfaceFilters || {};
state.surfaceFilters[target.dataset.surfaceFilter] = target.value || 'all';
render();
});
content.addEventListener('click', (event) => {
const graphFilter = event.target && event.target.closest && event.target.closest('[data-domain-graph-filter]');
if (graphFilter) {
const value = graphFilter.dataset.domainGraphFilter || 'all';
state.domainGraphFilter = GRAPH_FILTERS.has(value) ? value : 'all';
render();
return;
}
const graphGroup = event.target && event.target.closest && event.target.closest('[data-toggle-graph-group]');
if (graphGroup) {
const key = graphGroup.dataset.toggleGraphGroup || '';
if (key) {
const groups = graphCollapsedGroups();
if (graphGroup.dataset.graphGroupCollapsed === 'true') delete groups[key];
else groups[key] = true;
render();
}
return;
}
const toggle = event.target && event.target.closest && event.target.closest('[data-toggle-domain]');
if (toggle) {
const domain = toggle.dataset.toggleDomain || '';
const expanded = expandedDomains();
const isOpen = toggle.getAttribute && toggle.getAttribute('aria-expanded') === 'true';
expanded[domain] = !isOpen;
render();
return;
}
const button = event.target && event.target.closest && event.target.closest('[data-copy-surface]');
if (!button) return;
const value = button.dataset.copySurface || '';
const original = button.textContent;
const done = () => {
button.textContent = 'Copied';
setTimeout(() => { button.textContent = original || 'Copy'; }, 1000);
};
const clipboard = window.navigator && window.navigator.clipboard;
if (clipboard && typeof clipboard.writeText === 'function') {
clipboard.writeText(value).then(done, done);
} else {
done();
}
});
content.addEventListener('mouseover', (event) => {
const target = event.target && event.target.closest && (event.target.closest('[data-graph-focus-kind]') || event.target.closest('[data-graph-hover-participant]'));
if (!target) return;
if (event.relatedTarget && target.contains && target.contains(event.relatedTarget)) return;
applyGraphHover(graphHoverFromElement(target));
});
content.addEventListener('mouseout', (event) => {
const target = event.target && event.target.closest && (event.target.closest('[data-graph-focus-kind]') || event.target.closest('[data-graph-hover-participant]'));
if (!target) return;
if (event.relatedTarget && target.contains && target.contains(event.relatedTarget)) return;
const relatedTarget = event.relatedTarget && event.relatedTarget.closest && (event.relatedTarget.closest('[data-graph-focus-kind]') || event.relatedTarget.closest('[data-graph-hover-participant]'));
applyGraphHover(relatedTarget ? graphHoverFromElement(relatedTarget) : null);
});
}
render();
}
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', install);
else install();
window.addEventListener('feedBack:capabilities:ready', render);
window.addEventListener('feedBack:capabilities:changed', scheduleRender);
})();