// Offline cross-level conformance, not visual browser acceptance. const fs=require('fs'),path=require('path'),vm=require('vm'),assert=require('assert'); const repo=path.resolve(__dirname,'../../../..'); const site=path.join(repo,'docs'),wiki=path.join(repo,'src','docs'),DOCS='technical-documentation'; const full=fs.readFileSync(path.join(__dirname,'case_journeys.html'),'utf8'); const preview=fs.readFileSync(path.join(__dirname,'operations_preview.html'),'utf8'); const scripts=s=>[...s.matchAll(/'))[1]; for(const m of html.matchAll(/data-view="([^"]+)"/g)){const n=node('view-'+m[1]);n.dataset.view=m[1];views.push(n);} const context=vm.createContext({document:{getElementById:node,querySelector:()=>({open:false}),querySelectorAll:()=>views},location:{hash,search},history:{replaceState(){}},window:{addEventListener(){}},URLSearchParams}); for(const s of scripts(html))vm.runInContext(s,context); return {context,node,run:code=>vm.runInContext(code,context)}; } const a=harness(full),b=harness(preview); const configs=b.run('previewViews'); assert.equal(Object.keys(configs).length,6); for(const [key,value] of Object.entries(configs)){ b.run('choosePreviewView('+JSON.stringify(key)+')'); a.run("selected=cases.find(c=>c.id==="+JSON.stringify(value[1])+");resetStage();activeUid="+JSON.stringify(value[2])+";surface="+JSON.stringify(value[3])+";stateUid="+JSON.stringify(value[4])+";scenario='first';chatThreads[threadKey(active())]="+JSON.stringify(value[5])+";render();"); assert.equal(b.node('preview').innerHTML,a.node('preview').innerHTML,key+' frame is identical'); const url=new URL(b.node('preview-open').href,'https://fixture.invalid/'); const reopened=harness(full,url.search,url.hash); assert.equal(reopened.node('preview').innerHTML,b.node('preview').innerHTML,key+' deep link preserves the operation'); if(key==='decision'){ b.run("act('approve')");a.run("act('approve')"); assert.equal(b.node('preview').innerHTML,a.node('preview').innerHTML,'Decision action is identical at both levels'); } } const index=fs.readFileSync(path.join(site,'index.html'),'utf8'); const ui=fs.readFileSync(path.join(wiki,'10 Architecture/UI.md'),'utf8'); assert(index.includes('src="'+DOCS+'/10-architecture/ui/operations_preview.html?view=decision'),'the goals example embeds the configuration decision screen'); assert(ui.includes('src="ui/operations_preview.html"')); // the repeated hero panels are gone: one case is worked in the goals section instead, with its own // workflow strip and deep links to its remaining screens. assert(!index.includes('data-operations-accordion')&&!index.includes('data-accordion-trigger')&&!index.includes('case-intake.svg')); assert(index.includes('id="one-case"')&&index.includes('class="ge-tech"')&&index.includes('class="ge-zoom"')); const journeys=a.run('traces'); const knownLabels=new Map(); for(const t of Object.values(journeys))for(const s of [...t.journey.root,t.journey.closure,...t.journey.paths.first,...t.journey.auxiliary,...t.journey.linked]){ const key=s.module+'/'+s.stage;if(!knownLabels.has(key))knownLabels.set(key,new Set());knownLabels.get(key).add(s.label); } // The shared module flow strip was removed from the homepage in c1a371ef, so there are no // data-module-stage cells left to agree with; the loop stays because it guards the vocabulary // again the moment any of them come back. Its companion caption assertion went with the strip. for(const match of index.matchAll(/data-module-stage="([^"]+)"[^>]*>([^<]+)<\/b>/g))assert(knownLabels.get(match[1])?.has(match[2].replaceAll('&','&')),match[1]+' public vocabulary agrees'); assert(!index.includes('The same seven steps')); b.run("choosePreviewView('decision')"); assert(b.node('preview').innerHTML.includes('Mira D.')); assert(b.node('preview').innerHTML.includes('Ablera operator')); assert(b.node('preview').innerHTML.includes('Parent → child · PlanConfirmation')); assert(b.node('preview').innerHTML.includes('KOLHIDA_EUR · sub-plan')); assert(b.node('preview').innerHTML.includes('chat-composer')); const before=b.run('JSON.stringify(feedback)'); b.node('chat-text').value='Also check the rejected role '; b.run("act('send-chat')");assert(b.node('preview').innerHTML.includes('<script>')); assert.equal(b.run('JSON.stringify(feedback)'),before,'A chat message does not approve a gate'); b.run("act('approve')");assert.equal(b.run('JSON.stringify(feedback)'),before,'Unreconciled steering prevents approval of the old packet'); b.run("act('thread:template-1')");assert(b.node('preview').innerHTML.includes('MAXO_EUR · sub-plan')); assert(b.node('preview-open').href.includes('thread=template-1')); b.run("choosePreviewView('approval')");assert(b.node('preview').innerHTML.includes('Georgi S.'));assert(!b.node('preview').innerHTML.includes('id="chat-composer"')); b.run("choosePreviewView('result')");assert(!b.node('preview').innerHTML.includes('Abacus template task'));assert(b.node('preview').innerHTML.includes('Conversation with Ablera')); // the homepage no longer restates the CF-01 exception (removed with the flow strip in c1a371ef); // the case viewer is still the place it has to be right. const cf1=harness(full,'','#CF-01');assert(cf1.node('route-exception').innerHTML.includes('catalogue prefix')); const cfCatalog=fs.readFileSync(path.join(wiki,'10 Architecture/20 Configuration/Typical Cases.md'),'utf8'); const cf1paragraph=cfCatalog.match(/\*\*CF-01[^\n]+/)[0];assert(cf1paragraph.includes('Current illustrated path [P]'));assert(!cf1paragraph.includes('no shared rows'));assert(!cf1paragraph.includes('S3 apply on PROD')); console.log('Six chat/plan examples: shared frames/actions, operators, composer, nested parent confirmation, exact links, the goals-section case embed and CF-01 routing consistency pass. DOM stub only.');