Files
safetynet/scripts/ui-showcase.cjs
T

216 lines
26 KiB
JavaScript
Raw Normal View History

2026-10-13 10:33:31 +02:00
'use strict';
if (!process.versions.electron) {
const fs = require('node:fs'), path = require('node:path'), os = require('node:os');
const { spawn } = require('node:child_process');
const owned = fs.mkdtempSync(path.join(os.tmpdir(), 'safetynet-ui-showcase-'));
const env = { ...process.env, SAFETYNET_SHOWCASE_PROFILE: owned }; delete env.ELECTRON_RUN_AS_NODE;
const child = spawn(require('electron'), [__filename], { stdio: 'inherit', windowsHide: true, env });
const deadline = setTimeout(() => child.kill(), 90_000);
const finish = code => {
clearTimeout(deadline);
const resolved = path.resolve(owned);
if (!resolved.startsWith(path.resolve(os.tmpdir()) + path.sep) || !path.basename(resolved).startsWith('safetynet-ui-showcase-')) throw Error('Unsafe showcase cleanup');
fs.rmSync(resolved, { recursive: true, force: true, maxRetries: 10, retryDelay: 100 });
process.exit(code ?? 1);
};
child.once('error', () => finish(1)); child.once('exit', finish);
} else {
// Finite test harness for the real trusted shell. No production browser startup,
// site view, real credentials, network browsing or external application state.
const { app, BrowserWindow, ipcMain, nativeTheme } = require('electron');
const fs = require('node:fs');
const path = require('node:path');
const os = require('node:os');
const assert = require('node:assert/strict');
const { capturePNG } = require('../apps/browser/visual-capture.cjs');
app.disableHardwareAcceleration(); // Windows fixture uses the CPU compositor; production keeps its sandboxed defaults.
app.commandLine.appendSwitch('force-device-scale-factor', '1');
const root = path.resolve(__dirname, '..');
const profile = process.env.SAFETYNET_SHOWCASE_PROFILE || fs.mkdtempSync(path.join(os.tmpdir(), 'safetynet-ui-showcase-'));
if (!profile.startsWith(path.resolve(os.tmpdir()) + path.sep)) throw Error('Unsafe showcase profile');
app.setPath('userData', profile);
const output = path.join(root, 'artifacts');
fs.mkdirSync(output, { recursive: true });
const methods = ['organizeLibrary','applyLibraryOrganization','createTab','activateTab','closeTab','groupTab','mutateBookmarks','updateSettings','authorizeSetting','state', 'windowControls', 'setChromeLayout', 'shareURL', 'enroll', 'sso', 'navigate', 'back', 'forward', 'reload', 'ai', 'inspectFile', 'agentConnection', 'toggleAI', 'toggleVault', 'vaultStatus', 'vaultCreate', 'vaultUnlock', 'vaultLock', 'vaultList', 'vaultSave', 'vaultDelete', 'vaultFill', 'vaultGenerate'];
2026-10-13 10:33:31 +02:00
const preload = path.join(profile, 'showcase-preload.cjs');
fs.writeFileSync(preload, `const {contextBridge,ipcRenderer}=require('electron'); const methods=${JSON.stringify(methods)}; const api={};for(const method of methods)api[method]=(...args)=>ipcRenderer.invoke('showcase:call',method,args);api.onState=callback=>{const listener=(_event,state)=>callback(state);ipcRenderer.on('showcase:state',listener);return()=>ipcRenderer.removeListener('showcase:state',listener);};contextBridge.exposeInMainWorld('safetynet',api);`);
const fixtures = [
{ title: 'Apple Documentation', url: 'https://developer.apple.com/' },
{ title: 'GitHub', url: 'https://github.com/' },
{ title: 'MDN', url: 'https://developer.mozilla.org/' },
{ title: 'Wikipedia', url: 'https://wikipedia.org/' },
{ title: 'Company Intranet', url: 'https://intranet.example.test/' },
{ title: 'Design Library', url: 'https://design.example.test/' }
];
let state = { ready: true, enrolled: true, brand: { name: 'SafetyNet' }, url: 'https://example.test', pageTitle: 'Reference Workspace', pageColor: '#8aacb4', canNavigate: true, policyVersion: 12, message: 'Public fixture · centrally managed workspace', aiEnabled: true, agentsEnabled: true, documentScanEnabled: true, passwordManagerEnabled: true, vaultLocked: false };
let nextTab=1;state.tabsRevision=0;state.tabs=[{id:'showcase-native-1',url:state.url,title:state.pageTitle,color:state.pageColor,group:'',startPage:false,canGoBack:false,canGoForward:false}];state.activeTabId=state.tabs[0].id;
function snapshot(){const tab=state.tabs.find(tab=>tab.id===state.activeTabId);state={...state,url:tab.url,pageTitle:tab.title,pageColor:tab.color,tabsRevision:state.tabsRevision+1};return JSON.parse(JSON.stringify(state));}
function publish(sender){const result=snapshot();sender.send('showcase:state',result);return result;}
state.settings={available:true,visible:true,hiddenSections:[],lockedSections:[],appearance:{mode:'system',accent:'blue',tabMode:'separate'}};
state.bookmarks={managed:[{...fixtures[4],id:'showcase-company',folder:'Company'}],personal:{revision:0,items:fixtures.filter((_,i)=>i!==4).map((item,i)=>({...item,id:`showcase-${i}`,folder:'Work'}))},allowUserAdd:true,allowUserEdit:true,allowUserDelete:true,adminReadUserBookmarks:true};
let vaultLocked = false;
const records = [], screenshots = [], consoleErrors = [];
let window, finished = false;
function cleanup() {
if (path.resolve(profile).startsWith(path.resolve(os.tmpdir()) + path.sep) && path.basename(profile).startsWith('safetynet-ui-showcase-')) {
try { fs.rmSync(profile, { recursive: true, force: true }); } catch {}
}
}
if (!process.env.SAFETYNET_SHOWCASE_PROFILE) app.on('quit', cleanup);
const watchdog = setTimeout(() => { console.error('UI showcase exceeded 80 seconds'); app.exit(1); }, 80_000);
watchdog.unref();
ipcMain.handle('showcase:call', (event, method, args) => {
if (event.sender !== window?.webContents || !methods.includes(method)) return { ok: false, error: 'Showcase operation refused' };
let data = null;
const navigation=typeof args[0]==='object'?args[0]:{url:args[0],tabId:state.activeTabId};
if(method==='navigate'&&navigation.url==='https://example.test/slow'){const id=navigation.tabId;return new Promise(resolve=>setTimeout(()=>{const tab=state.tabs.find(tab=>tab.id===id);if(tab){tab.url=navigation.url;tab.title='Delayed public fixture';tab.startPage=false;}resolve({ok:true,data:publish(event.sender)});},250));}
if (method === 'state' || ['enroll', 'sso', 'back', 'forward', 'reload'].includes(method)) data = snapshot();
else if(method==='createTab'){const tab={id:`showcase-native-${++nextTab}`,url:'',title:'New Tab',group:args[0]?.group||'',startPage:true,color:'#b8c7dd',canGoBack:false,canGoForward:false};state.tabs.push(tab);state.activeTabId=tab.id;data=publish(event.sender);}
else if(method==='activateTab'){assert.ok(state.tabs.some(tab=>tab.id===args[0]));state.activeTabId=args[0];data=publish(event.sender);}
else if(method==='closeTab'){const index=state.tabs.findIndex(tab=>tab.id===args[0]);assert.ok(index>=0);state.tabs.splice(index,1);if(!state.tabs.length){state.tabs.push({id:`showcase-native-${++nextTab}`,url:'',title:'New Tab',group:'',startPage:true,color:'#b8c7dd',canGoBack:false,canGoForward:false});}if(state.activeTabId===args[0])state.activeTabId=state.tabs[Math.min(index,state.tabs.length-1)].id;data=publish(event.sender);}
else if(method==='organizeLibrary'){assert.ok(state.libraryAI?.suggestionsEnabled);data={proposalId:'fixture-review-id',plan:{bookmarks:[{id:'showcase-1',folder:'Engineering'}],tabs:[]}};}
else if(method==='applyLibraryOrganization'){assert.equal(args[0].proposalId,'fixture-review-id');assert.equal(args[0].confirm,true);state.bookmarks.personal.items.find(item=>item.id==='showcase-1').folder='Engineering';state.bookmarks.personal.revision++;data={completed:true};}
2026-10-13 10:33:31 +02:00
else if(method==='groupTab'){assert.ok(state.settings.visible&&!state.settings.hiddenSections.includes('library')&&!state.settings.lockedSections.includes('library'));const tab=state.tabs.find(tab=>tab.id===args[0].id);assert.ok(tab);tab.group=args[0].group;data=publish(event.sender);}
else if(method==='mutateBookmarks'){const input=args[0];if(!state.settings.visible||state.settings.hiddenSections.includes('library')||state.settings.lockedSections.includes('library')||input.expectedRevision!==state.bookmarks.personal.revision)return {ok:false,error:'Managed bookmark refused'};assert.equal(input.action,'add');state.bookmarks.personal={revision:state.bookmarks.personal.revision+1,items:[...state.bookmarks.personal.items,input.bookmark]};data=state;event.sender.send('showcase:state',state);}
else if(method==='updateSettings'){if(!state.settings.available||!state.settings.visible||state.settings.hiddenSections.includes('appearance')||state.settings.lockedSections.includes('appearance'))return{ok:false,error:'Managed setting refused'};state.settings={...state.settings,appearance:{...args[0]}};data=state.settings;}
else if(method==='authorizeSetting'){if(!state.settings.available||!state.settings.visible||state.settings.hiddenSections.includes(args[0])||state.settings.lockedSections.includes(args[0]))return{ok:false,error:'Managed setting refused'};data={allowed:true};}
else if (method === 'navigate') { const url = new URL(navigation.url); assert.equal(url.protocol, 'https:');const tab=state.tabs.find(tab=>tab.id===navigation.tabId);assert.ok(tab);tab.url=url.href;tab.title=fixtures.find(item=>item.url===url.href)?.title||'Reference Workspace';tab.startPage=false;data=publish(event.sender); }
else if (method === 'setChromeLayout') {
const layout = args[0]; assert.ok([64, 104].includes(layout.height));
for (const key of ['sidebarOpen', 'startPageOpen', 'overviewOpen', 'toolsOpen']) assert.equal(typeof layout[key], 'boolean');
records.push({ method, ...layout, nativeHidden: layout.startPageOpen || layout.overviewOpen || layout.toolsOpen }); data = { applied: true };
} else if (method === 'windowControls') { assert.ok(['minimize', 'maximize', 'close'].includes(args[0])); records.push({ method, action: args[0], nativeFlag: false }); data = { recorded: true }; }
else if (method === 'shareURL') data = { shared: true };
else if (method === 'toggleAI' || method === 'toggleVault') { records.push({ method, open: args[0] }); data = { applied: true }; }
else if (method === 'vaultStatus') data = { enabled: true, exists: true, locked: vaultLocked, allowSaving: true, allowAutofill: true, idleLockSeconds: 300 };
else if (method === 'vaultList') data = [{ id: 'public-fixture', origin: 'https://example.test', username: 'example-user', label: 'Public demonstration account' }];
else if (method === 'vaultLock') { vaultLocked = true; data = { locked: true }; }
else if (method === 'vaultCreate' || method === 'vaultUnlock') { vaultLocked = false; data = { locked: false }; }
else if (method === 'vaultGenerate') data = 'PUBLIC-DEMONSTRATION-ONLY';
else if (method === 'vaultSave' || method === 'vaultDelete' || method === 'vaultFill') data = { completed: true };
else if (method === 'ai') data = { answer: 'Public local fixture response.' };
else if (method === 'agentConnection') data = { address: 'http://127.0.0.1:0', credentialPath: 'Public showcase · no pairing credential' };
return { ok: true, data };
});
const delay = milliseconds => new Promise(resolve => setTimeout(resolve, milliseconds));
async function execute(source) { return window.webContents.executeJavaScript(source, true); }
async function reloadShell() {
const loaded = new Promise((resolve, reject) => {
const timer = setTimeout(() => reject(Error('Showcase shell reload timed out')), 5000);
window.webContents.once('did-finish-load', () => { clearTimeout(timer); resolve(); });
});
window.webContents.reload(); await loaded; await delay(100);
}
async function waitFor(source, expected) {
for (let attempt = 0; attempt < 40; attempt++) { if (await execute(source) === expected) return; await delay(70); }
assert.equal(await execute(source), expected);
}
async function click(id) { await execute(`document.getElementById(${JSON.stringify(id)}).click()`); await delay(100); }
async function appearance(theme, mode) {
await execute(`for(const [id,value] of ${JSON.stringify([['theme-mode', theme], ['tab-mode', mode]])}){const element=document.getElementById(id);element.value=value;element.dispatchEvent(new Event('change',{bubbles:true}));}`); await delay(100);
}
async function accent(value) {
await execute(`(()=>{const selector=document.getElementById('accent-mode');selector.value=${JSON.stringify(value)};selector.dispatchEvent(new Event('change',{bubbles:true}));})()`); await delay(90);
}
async function shot(name) {
console.log('Showcase capture: ' + name);
await execute('document.fonts.ready'); await delay(90);
const measurements = await execute(`(()=>{const rect=id=>{const element=document.getElementById(id),r=element.getBoundingClientRect();return{hidden:element.hidden,x:r.x,y:r.y,width:r.width,height:r.height}};const brokenImages=[...document.images].filter(image=>image.hasAttribute('src')&&(!image.complete||!image.naturalWidth)).map(image=>image.getAttribute('src'));return{viewport:{width:innerWidth,height:innerHeight,zoom:devicePixelRatio},theme:document.documentElement.dataset.theme,effectiveTheme:document.documentElement.dataset.effectiveTheme,accent:document.documentElement.dataset.accent,tabMode:document.documentElement.dataset.tabMode,chrome:rect('chrome'),toolbar:document.querySelector('.toolbar').getBoundingClientRect().height,library:rect('library'),ai:rect('ai'),vault:rect('vault'),startPage:rect('start-page'),overview:rect('overview'),tools:rect('tools-menu'),scrollWidth:document.documentElement.scrollWidth,brokenImages,error:document.getElementById('error').textContent,passwordFieldsEmpty:[...document.querySelectorAll('input[type=password]')].every(input=>!input.value)}})()`);
2026-10-13 10:33:31 +02:00
assert.equal(measurements.toolbar, 64); assert.ok([64, 104].includes(measurements.chrome.height));
assert.equal(measurements.scrollWidth, measurements.viewport.width); assert.deepEqual(measurements.brokenImages, []); assert.equal(measurements.passwordFieldsEmpty, true); assert.equal(measurements.error, '');
if (!measurements.library.hidden) assert.equal(measurements.library.width, 240);
for (const panel of ['ai', 'vault']) if (!measurements[panel].hidden) assert.equal(measurements[panel].width, 360);
// This Windows host's native capturePage can return an earlier compositor frame.
// Exercise the existing trusted screenshot-only CDP fallback for finite fixtures.
const capturePage = window.webContents.capturePage;
let png;
try { window.webContents.capturePage = async () => { throw Error('Use finite showcase compositor fallback'); }; png = await capturePNG(window.webContents); }
finally { window.webContents.capturePage = capturePage; }
fs.writeFileSync(path.join(output, name + '.png'), png); screenshots.push({ name, ...measurements });
}
app.whenReady().then(async () => {
window = new BrowserWindow({ width: 1320, height: 900, useContentSize: true, frame: false, show: true, backgroundColor: '#f7f8fa', webPreferences: { preload, contextIsolation: true, sandbox: true, nodeIntegration: false, zoomFactor: 1 } });
window.webContents.setZoomFactor(1);
window.webContents.on('console-message', event => { if (event.level === 'error' || event.level >= 3) consoleErrors.push(event.message); });
window.webContents.on('will-navigate', event => event.preventDefault());
window.webContents.setWindowOpenHandler(() => ({ action: 'deny' }));
await window.loadFile(path.join(root, 'apps/browser/index.html'));
await execute(`localStorage.clear();localStorage.setItem('sbrowser.bookmarks',${JSON.stringify(JSON.stringify(fixtures))});localStorage.setItem('sbrowser.theme','"light"');localStorage.setItem('sbrowser.tab-mode','"separate"');localStorage.setItem('sbrowser.accent','{"toString":0}');`);
await reloadShell();
assert.equal(await execute(`document.documentElement.dataset.accent`), 'blue');
await appearance('auto', 'separate'); nativeTheme.themeSource = 'dark';
await waitFor(`document.documentElement.dataset.effectiveTheme`, 'dark');
nativeTheme.themeSource = 'light';
await waitFor(`document.documentElement.dataset.effectiveTheme`, 'light');
await appearance('dark', 'separate'); nativeTheme.themeSource = 'light'; await delay(90);
assert.equal(await execute(`document.body.dataset.theme`), 'dark');
await accent('teal'); await reloadShell();
assert.equal(await execute(`document.documentElement.dataset.accent`), 'teal');
assert.equal(await execute(`document.documentElement.dataset.theme`), 'dark');
assert.equal(await execute(`document.documentElement.style.getPropertyValue('--accent')`), '#007d76');
await accent('blue'); await appearance('light', 'separate');
// Real DOM interactions verify tabs, bookmarks, themes and overlay signaling.
await click('tools-toggle'); await click('bookmark');
await execute(`document.getElementById('bookmark-form').requestSubmit()`);
await waitFor(`document.getElementById('bookmark-editor').open`,false);
assert.equal(state.bookmarks.managed.length+state.bookmarks.personal.items.length,7);
assert.equal(await execute(`JSON.parse(localStorage.getItem('sbrowser.bookmarks')).length`), 6,'User bookmarks must not be mutated through renderer storage');
await click('new-tab'); assert.equal(await execute(`document.getElementById('library').hidden`),true,'Sidebar starts collapsed');
2026-10-13 10:33:31 +02:00
assert.equal(await execute(`document.querySelectorAll('#favorites-grid .favorite-card').length`), 7);
await click('sidebar-toggle');await execute(`document.getElementById('group-name').value='Work'`);await click('group-add');assert.equal(state.tabs.find(t=>t.id===state.activeTabId).group,'Work');await execute(`document.getElementById('group-name').value='Research';document.querySelector('[aria-label="Rename group Work"]').click()`);await waitFor(`document.querySelector('[aria-label="Ungroup tabs in Research"]')!==null`,true);assert.equal(state.tabs.find(t=>t.id===state.activeTabId).group,'Research');await execute(`document.querySelector('[aria-label="Ungroup tabs in Research"]').click()`);await waitFor(`document.querySelector('[aria-label="Ungroup tabs in Research"]')===null`,true);assert.equal(state.tabs.find(t=>t.id===state.activeTabId).group,'');await click('sidebar-toggle');
const aligned=await execute(`(()=>{const cards=[...document.querySelectorAll('#favorites-grid .favorite-card')];const first=cards[0].getBoundingClientRect().top;return cards.filter(c=>c.getBoundingClientRect().top===first).map(c=>c.querySelector('.favorite-icon').getBoundingClientRect().top)})()`);assert.ok(aligned.length>1);assert.ok(aligned.every(top=>Math.abs(top-aligned[0])<1),'Managed and personal favorites share icon baseline');
await click('favorites-manage');assert.equal(await execute(`document.getElementById('bookmark-manager').open`),true);assert.equal(await execute(`document.querySelectorAll('#manager-list .manager-row').length`),7);assert.equal(await execute(`document.querySelector('#manager-list [data-bookmark-id="showcase-company"] .manager-row-actions')===null`),true);assert.equal(await execute(`document.getElementById('manager-ai').disabled`),true);await execute(`document.getElementById('bookmark-search').value='github';document.getElementById('bookmark-search').dispatchEvent(new Event('input'))`);assert.equal(await execute(`document.querySelectorAll('#manager-list .manager-row').length`),1);await shot('sbrowser-bookmark-manager');state.libraryAI={suggestionsEnabled:true,autoApplyEnabled:false,busy:false};window.webContents.send('showcase:state',snapshot());await delay(100);await click('manager-ai');assert.equal(await execute(`document.getElementById('manager-proposal').hidden`),false);assert.match(await execute(`document.getElementById('manager-diff').textContent`),/GitHub.*Engineering/);assert.equal(state.bookmarks.personal.items.find(item=>item.id==='showcase-1').folder,'Work','Preview has no side effects');await click('manager-apply');assert.equal(state.bookmarks.personal.items.find(item=>item.id==='showcase-1').folder,'Engineering');await click('bookmark-manager-close');
2026-10-13 10:33:31 +02:00
await shot('sbrowser-light');
state.brand={name:'Northstar Company',color:'#166b68',logoDataUrl:'data:image/png;base64,'+fs.readFileSync(path.join(root,'docs/brand/safetynet-icon.png')).toString('base64')};
state.startPage={title:'Welcome to Northstar.',subtitle:'Your company workspace, news and favorite tools.',newsEnabled:true,news:[{id:'news-1',title:'Company update: our new intranet is here',url:'https://intranet.example.test/news',publishedAt:'2026-10-13T12:00:00Z'},{id:'news-2',title:'Security week: join the local AI workshop',url:'https://intranet.example.test/events'}]};
window.webContents.send('showcase:state',snapshot());await delay(150);
assert.equal(await execute(`document.getElementById('start-brand-name').textContent`),'Northstar Company');
assert.equal(await execute(`document.querySelectorAll('#company-news-items .company-news-card').length`),2);
assert.equal(await execute(`document.getElementById('start-company-logo').hidden`),false);
await shot('sbrowser-company-branding');
state.startPage={newsEnabled:false};state.brand={name:'SafetyNet'};window.webContents.send('showcase:state',snapshot());await delay(100);
assert.equal(await execute(`document.getElementById('company-news').hidden`),true);assert.equal(await execute(`document.getElementById('start-company-logo').hasAttribute('src')`),false);
2026-10-13 10:33:31 +02:00
await appearance('dark', 'compact'); await shot('sbrowser-dark');
await click('tab-overview'); assert.equal(await execute(`document.querySelectorAll('#overview-grid .overview-card').length`), 2); await shot('sbrowser-overview');
await click('overview-close'); await appearance('light', 'separate'); await click('tools-toggle'); await shot('sbrowser-settings');
await appearance('dark', 'separate'); await accent('violet'); await shot('sbrowser-theme-violet');
await appearance('light', 'separate'); await accent('blue');
await click('passwords'); await shot('sbrowser-vault'); await click('vault-close');
await appearance('light', 'compact'); window.setContentSize(800, 650); await delay(150); await shot('sbrowser-compact');
await click('tools-toggle'); await click('ai-toggle'); await shot('sbrowser-compact-panels'); await click('ai-close');window.setContentSize(480,740);await delay(120);await shot('sbrowser-narrow');window.setContentSize(800,650);
2026-10-13 10:33:31 +02:00
await execute(`document.getElementById('address').value='https://example.test/slow';document.getElementById('address-form').requestSubmit();`);
await click('new-tab'); await delay(300);
assert.equal(await execute(`document.querySelector('.browser-tab.active .tab-label').textContent`), 'New Tab');
assert.equal(await execute(`document.getElementById('address').value`), '');
assert.equal(await execute(`document.getElementById('start-page').hidden`), false);
const latestLayout=records.findLast(record=>record.method==='setChromeLayout');assert.ok(latestLayout.startPageOpen&&latestLayout.nativeHidden);
for (const overlay of ['startPageOpen', 'overviewOpen', 'toolsOpen']) assert.ok(records.some(record => record.method === 'setChromeLayout' && record[overlay] && record.nativeHidden));
// Policy updates override stale or hostile renderer preferences immediately.
state={...state,settings:{...state.settings,lockedSections:['appearance','ai','passwords','documentScan','agents'],appearance:{mode:'dark',accent:'violet',tabMode:'compact'}}};
window.webContents.send('showcase:state',state);await delay(120);
await execute(`localStorage.setItem('sbrowser.theme','"light"');localStorage.setItem('sbrowser.accent','"blue"');window.dispatchEvent(new StorageEvent('storage',{key:'sbrowser.theme'}));`);
assert.equal(await execute(`document.body.dataset.theme`),'dark');
assert.equal(await execute(`document.documentElement.dataset.accent`),'violet');
assert.equal(await execute(`['theme-mode','accent-mode','tab-mode','ai-toggle','passwords','inspect','agents'].every(id=>document.getElementById(id).disabled)`),true);
assert.equal((await execute(`window.safetynet.updateSettings({mode:'light',accent:'blue',tabMode:'separate'})`)).ok,false);
state={...state,settings:{...state.settings,lockedSections:[],hiddenSections:['appearance','ai','passwords','documentScan','agents'],visible:true}};
window.webContents.send('showcase:state',state);await delay(100);
assert.equal(await execute(`['appearance-section','ai-toggle','sidebar','passwords','inspect','agents'].every(id=>document.getElementById(id).hidden)`),true);
state={...state,settings:{...state.settings,visible:false}};window.webContents.send('showcase:state',state);await delay(100);
assert.equal(await execute(`document.getElementById('tools-toggle').hidden&&document.getElementById('tools-menu').hidden`),true);
state={...state,settings:{...state.settings,hiddenSections:['appearance']}};window.webContents.send('showcase:state',state);await delay(100);
assert.equal(await execute(`!document.getElementById('quick-ai').hidden&&!document.getElementById('quick-ai').disabled&&document.getElementById('appearance-section').hidden`),true);
await click('quick-ai');assert.equal(await execute(`document.getElementById('ai').hidden`),false);await click('ai-close');
await reloadShell();assert.equal(await execute(`document.getElementById('tools-toggle').hidden&&document.body.dataset.theme==='dark'`),true);
await execute(`document.getElementById('error').textContent=''`);
for (const control of ['window-minimize', 'window-maximize', 'window-close']) await click(control);
assert.equal(records.filter(record => record.method === 'windowControls').length, 3);
fs.writeFileSync(path.join(output, 'sbrowser-ui-layout.json'), JSON.stringify({ fixtureOnly: true, managedSettingsProof:{liveLocks:true,hiddenSections:true,globalHidden:true,localStorageCannotOverride:true,reloadPreservesPolicy:true,directUpdateRejected:true,globalHiddenPreservesApprovedFeatures:true},screenshots, records, consoleErrors }, null, 2));
assert.deepEqual(consoleErrors, []);
finished = true; clearTimeout(watchdog); console.log('UI showcase passed: ten screenshots, live system themes, persisted accents, real DOM interactions and delayed-navigation regression.');
2026-10-13 10:33:31 +02:00
if(window.webContents.debugger.isAttached())window.webContents.debugger.detach();
window.destroy(); app.exit(0);
}).catch(error => { clearTimeout(watchdog); console.error(error.message); if (!finished) app.exit(1); });
}