WIP: Vendoo 1.44.0 – UX Foundation & Guided Workspaces #29

Draft
Masterluke77 wants to merge 284 commits from release/1.44.0-ux-foundation-production-update-v1 into main
Showing only changes of commit 7dd0be2e8a - Show all commits
@@ -0,0 +1,184 @@
import fs from 'node:fs';
import path from 'node:path';
import vm from 'node:vm';
const root = process.cwd();
const read = relative => fs.readFileSync(path.join(root, relative), 'utf8');
const adapters = read('public/safe-action-adapters.js');
const loader = read('public/module-extension-workspace.js');
const pkg = JSON.parse(read('package.json'));
const failures = [];
const expect = (condition, message) => { if (!condition) failures.push(message); };
const includesAll = (source, values, label) => values.forEach(value => expect(source.includes(value), `${label}: ${value}`));
includesAll(adapters, [
'uxSafeActionAdaptersReady',
'uxSafeActionAdaptersRegistered',
'window.VendooActions',
'actions.registerMany',
"surfaces: ['command-palette']",
"danger: 'none'",
'vendoo:actions-ready',
'vendoo:safe-action-adapters-ready',
'workspace:modules:refresh',
'workspace:modules:filter-all',
'workspace:modules:filter-problems',
'workspace:articles:open-current',
'workspace:articles:view-cards',
'workspace:articles:view-list',
'workspace:media:open-current-preview',
'workspace:media:density-comfortable',
'workspace:media:reset-filters',
'#module-manager-refresh',
'[data-article-view-choice="cards"]',
'[data-article-view-choice="list"]',
'[data-media-density-option="comfortable"]',
'.ux-media-filter-reset',
'focusin',
'pointerdown',
], 'Safe Action Adapter-Vertrag fehlt');
includesAll(loader, [
'loadCommandPlatformAssets',
'loadActionRegistryAssets',
'loadGlobalNavigationAssets',
'loadSafeActionAdapters',
'safe-action-adapters.js?v=1.44.0',
'data-ux-safe-action-adapters',
'loadSafeActionAdapters();',
], 'Safe-Action-Loader fehlt');
expect(loader.indexOf('loadActionRegistryAssets();') < loader.indexOf('loadSafeActionAdapters();'), 'Action Registry muss vor Safe Action Adapters geladen werden');
expect(loader.indexOf('loadGlobalNavigationAssets();') < loader.indexOf('loadSafeActionAdapters();'), 'Globale Navigation muss vor Safe Action Adapters geladen werden');
expect(pkg.scripts?.['verify:safe-action-adapters'] === 'node tools/verify-safe-action-adapters-1.44.0.mjs', 'verify:safe-action-adapters fehlt');
expect(pkg.scripts?.['verify:all']?.includes('npm run verify:safe-action-adapters'), 'verify:all enthält Safe-Action-Gate nicht');
expect(!/\.innerHTML\s*=/.test(adapters), 'Safe Action Adapters dürfen innerHTML nicht setzen');
expect(!/\bfetch\s*\(/.test(adapters), 'Safe Action Adapters dürfen keine Netzwerkaufrufe ausführen');
expect(!/\bconfirm\s*\(/.test(adapters), 'Safe Action Adapters dürfen window.confirm nicht verwenden');
expect(!/setInterval\s*\(/.test(adapters), 'Safe Action Adapters dürfen kein Polling starten');
expect(!/Vendoo(?:ModuleManager|App|Api)\.(?:publish|delete|remove|install|enable|disable|start|stop)/i.test(adapters), 'Safe Action Adapters dürfen keine produktive Fachlogik direkt aufrufen');
expect(!/danger:\s*['"](?:caution|destructive)['"]/.test(adapters), 'Dieser Slice darf keine vorsichtigen oder destruktiven Actions registrieren');
function element({ id = '', selector = '', parent = null } = {}) {
return {
id,
selector,
hidden: false,
disabled: false,
isConnected: true,
clicks: 0,
classList: { contains: () => false },
getAttribute: () => null,
click() { this.clicks += 1; },
closest(query) {
if (query === '#history-list .history-row' && parent?.kind === 'article-row') return parent;
if (query === '[data-media-preview]' && this.selector === '[data-media-preview]') return this;
if (query === '.history-row' && parent?.kind === 'article-row') return parent;
if (query === '.media-card' && parent?.kind === 'media-card') return parent;
return null;
},
querySelector(query) { return parent?.querySelector?.(query) || null; },
};
}
function verifyRuntime() {
const registered = [];
const documentListeners = new Map();
const windowListeners = new Map();
const events = [];
const rootElement = { dataset: {} };
const moduleSection = element({ id: 'admin-modules-panel' });
const historySection = element({ id: 'history' });
const mediaSection = element({ id: 'media-library' });
const refreshButton = element({ selector: '#module-manager-refresh' });
const moduleAll = element({ selector: '[data-ux-module-group="modules"][data-ux-module-filter="all"]' });
const moduleProblems = element({ selector: '[data-ux-module-group="modules"][data-ux-module-filter="problems"]' });
const cardsButton = element({ selector: '[data-article-view-choice="cards"]' });
const listButton = element({ selector: '[data-article-view-choice="list"]' });
const densityButton = element({ selector: '[data-media-density-option="comfortable"]' });
const mediaReset = element({ selector: '.ux-media-filter-reset' });
const articleTitle = element({ selector: '.history-article-title' });
const articleRow = {
kind: 'article-row',
hidden: false,
disabled: false,
isConnected: true,
getAttribute: () => null,
querySelector: query => query === '.history-article-title' ? articleTitle : null,
};
const articleTarget = element({ parent: articleRow });
const mediaPreview = element({ selector: '[data-media-preview]' });
const selectorMap = new Map([
['#module-manager-refresh', refreshButton],
['[data-ux-module-group="modules"][data-ux-module-filter="all"]', moduleAll],
['[data-ux-module-group="modules"][data-ux-module-filter="problems"]', moduleProblems],
['[data-article-view-choice="cards"]', cardsButton],
['[data-article-view-choice="list"]', listButton],
['[data-media-density-option="comfortable"]', densityButton],
['.ux-media-filter-reset', mediaReset],
]);
const sectionMap = new Map([
['admin-modules-panel', moduleSection],
['history', historySection],
['media-library', mediaSection],
]);
const documentObject = {
documentElement: rootElement,
addEventListener(type, listener) { documentListeners.set(type, listener); },
getElementById(id) { return sectionMap.get(id) || null; },
querySelector(selector) { return selectorMap.get(selector) || null; },
};
const windowObject = {
VendooActions: {
registerMany(actions) { registered.push(...actions); },
refresh() {},
},
getComputedStyle: () => ({ display: 'block', visibility: 'visible' }),
addEventListener(type, listener) { windowListeners.set(type, listener); },
dispatchEvent(event) { events.push(event); return true; },
};
const context = vm.createContext({
window: windowObject,
document: documentObject,
CustomEvent: class CustomEvent { constructor(type, init = {}) { this.type = type; this.detail = init.detail; } },
console,
});
vm.runInContext(adapters, context, { filename: 'public/safe-action-adapters.js' });
expect(registered.length === 9, `Runtime: erwartet 9 Adapter-Actions, erhalten ${registered.length}`);
expect(new Set(registered.map(action => action.id)).size === registered.length, 'Runtime: Action-IDs sind nicht eindeutig');
expect(registered.every(action => action.danger === 'none'), 'Runtime: nicht sichere Gefahrstufe registriert');
expect(registered.every(action => action.surfaces?.includes('command-palette')), 'Runtime: Command-Palette-Surface fehlt');
const byId = id => registered.find(action => action.id === id);
expect(byId('workspace:modules:refresh')?.handler() === true && refreshButton.clicks === 1, 'Runtime: Modul-Refresh delegiert nicht an bestehenden Button');
expect(byId('workspace:modules:filter-problems')?.handler() === true && moduleProblems.clicks === 1, 'Runtime: Problemfilter delegiert nicht an bestehenden Button');
expect(byId('workspace:articles:view-list')?.handler() === true && listButton.clicks === 1, 'Runtime: Listenansicht delegiert nicht an bestehenden Button');
expect(byId('workspace:media:density-comfortable')?.handler() === true && densityButton.clicks === 1, 'Runtime: Galeriedichte delegiert nicht an bestehenden Button');
expect(byId('workspace:media:reset-filters')?.handler() === true && mediaReset.clicks === 1, 'Runtime: Medienfilter-Reset delegiert nicht an bestehenden Button');
documentListeners.get('focusin')?.({ target: articleTarget });
expect(byId('workspace:articles:open-current')?.enabled() === true, 'Runtime: fokussierter Artikel wird nicht als ausführbar erkannt');
expect(byId('workspace:articles:open-current')?.handler() === true && articleTitle.clicks === 1, 'Runtime: Artikel öffnen delegiert nicht an bestehenden Titel-Handler');
documentListeners.get('pointerdown')?.({ target: mediaPreview });
expect(byId('workspace:media:open-current-preview')?.enabled() === true, 'Runtime: fokussierte Medienvorschau wird nicht als ausführbar erkannt');
expect(byId('workspace:media:open-current-preview')?.handler() === true && mediaPreview.clicks === 1, 'Runtime: Medienvorschau delegiert nicht an bestehenden Preview-Handler');
expect(events.some(event => event.type === 'vendoo:safe-action-adapters-ready' && event.detail?.count === 9), 'Runtime: Ready-Event fehlt');
}
verifyRuntime();
if (failures.length) {
console.error('Safe Action Adapter Gate fehlgeschlagen:');
failures.forEach(failure => console.error(`- ${failure}`));
process.exit(1);
}
console.log('Safe Action Adapter Gate bestanden.');