feat: add evidence-led Reveal presentation and homepage invitation

This commit is contained in:
jarvis-at-skic committed 2026-10-08 17:41:24 +00:00
1 parent fcaa84f3bf
commit 9a7766b323
12 files changed
+271 -3

No files matched your search

+51
View File
@@ -0,0 +1,51 @@
const {chromium}=require('playwright');
const fs=require('node:fs'); const assert=require('node:assert/strict');
(async()=>{
const base=process.argv[2]||'http://127.0.0.1:4179'; const root='/eldorado-v1/documentation/';const url=base+root+'presentations/less-prompting-more-shipping/';
const out='.cache/presentation';fs.mkdirSync(out,{recursive:true});
const browser=await chromium.launch({headless:true,args:['--no-sandbox'],executablePath:process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE});
const results=[];
try{
for(const viewport of [{width:1440,height:900},{width:390,height:844},{width:844,height:390}]){
const context=await browser.newContext({viewport,hasTouch:viewport.width<1000,reducedMotion:'reduce'});
const page=await context.newPage();const errors=[];page.on('pageerror',e=>errors.push(e.message));page.on('response',r=>{if(r.status()>=400)errors.push(`${r.status()} ${r.url()}`)});page.on('console',m=>{if(m.type()==='error')errors.push(m.text())});
assert.equal((await page.goto(url,{waitUntil:'networkidle'})).status(),200);
await page.waitForFunction(()=>window.Reveal?.isReady());await page.evaluate(()=>document.fonts.ready);
assert.equal(await page.evaluate(()=>Reveal.getTotalSlides()),19);
assert.equal(await page.evaluate(()=>Reveal.getConfig().transition),'none');
const bounds=[];
for(let i=0;i<19;i++){
await page.evaluate(i=>Reveal.slide(i),i);await page.waitForTimeout(60);
const bad=await page.evaluate(()=>{
const slide=Reveal.getCurrentSlide(),r=slide.getBoundingClientRect();
return [...slide.querySelectorAll('h1,h2,p,li,td,th,.rail,.gate,.facts,.flow,.roles,.timeline,.capacity,.measure-list,.contrast,.comparison,.choices,.ledger')].filter(e=>!e.closest('.notes')).filter(e=>{const b=e.getBoundingClientRect();return b.bottom>r.bottom+1||b.right>r.right+1||b.left<r.left-1||b.top<r.top-1}).map(e=>e.textContent.slice(0,60));
});
bounds.push({slide:i+1,overflow:bad});
await page.screenshot({path:`${out}/${viewport.width}-slide-${String(i+1).padStart(2,'0')}.png`});
}
fs.writeFileSync(`${out}/${viewport.width}-bounds.json`,JSON.stringify(bounds,null,2));
assert.deepEqual(bounds.filter(b=>b.overflow.length),[],'slide overflow');
await page.goto(url+'#/qualification');await page.reload();await page.waitForFunction(()=>Reveal.isReady());assert.equal(await page.evaluate(()=>Reveal.getCurrentSlide().id),'qualification');
await page.keyboard.press('ArrowRight');assert.equal(await page.evaluate(()=>Reveal.getCurrentSlide().id),'inconclusive');await page.keyboard.press('ArrowLeft');
await page.getByRole('button',{name:'Overview',exact:true}).click();assert(await page.evaluate(()=>Reveal.isOverview()));await page.keyboard.press('Escape');assert(!await page.evaluate(()=>Reveal.isOverview()));
if(viewport.width===1440){
await page.getByRole('button',{name:'Fullscreen',exact:true}).click();await page.waitForFunction(()=>!!document.fullscreenElement);await page.getByRole('button',{name:'Fullscreen',exact:true}).click();await page.waitForFunction(()=>!document.fullscreenElement);
const popupPromise=context.waitForEvent('page');await page.getByRole('button',{name:'Notes',exact:true}).click();const popup=await popupPromise;await popup.waitForLoadState();await popup.waitForFunction(()=>document.body.innerText.includes('The author cannot approve'),{timeout:10000});await popup.screenshot({path:`${out}/speaker-view.png`});await popup.close();
} else {
const client=await context.newCDPSession(page);
const before=await page.evaluate(()=>Reveal.getIndices().h);
await client.send('Input.dispatchTouchEvent',{type:'touchStart',touchPoints:[{x:viewport.width*.8,y:viewport.height*.5}]});
await client.send('Input.dispatchTouchEvent',{type:'touchMove',touchPoints:[{x:viewport.width*.2,y:viewport.height*.5}]});
await client.send('Input.dispatchTouchEvent',{type:'touchEnd',touchPoints:[]});
assert.equal(await page.evaluate(()=>Reveal.getIndices().h),before+1);
}
const links=await page.locator('a').evaluateAll(els=>els.map(e=>e.href).filter(h=>!h.includes('?')));
for(const link of new Set(links))assert.equal((await page.request.get(link)).status(),200,link);
await page.getByRole('link',{name:'Portal',exact:true}).click();await page.waitForURL(base+root);await page.getByRole('link',{name:/Open presentation/}).scrollIntoViewIfNeeded();await page.screenshot({path:`${out}/${viewport.width}-homepage.png`});await page.getByRole('link',{name:/Open presentation/}).click();await page.waitForFunction(()=>window.Reveal?.isReady());
assert.deepEqual(errors,[]);results.push({viewport,slides:19,overflow:false,errors,deepLinkRefresh:true,keyboard:true,overview:true,links:true,touch:viewport.width!==1440});await context.close();
}
const page=await browser.newPage({viewport:{width:1440,height:900}});await page.goto(url+'?print-pdf',{waitUntil:'networkidle'});await page.waitForFunction(()=>document.querySelectorAll('.pdf-page').length===19);await page.pdf({path:out+'/print.pdf',preferCSSPageSize:true,printBackground:true});
assert.equal(await page.locator('.pdf-page').count(),19);
fs.writeFileSync(`${out}/results.json`,JSON.stringify({results,printPages:19},null,2));console.log('PASS: 57 slide views, bounds, refresh/hash, keyboard, overview, touch, fullscreen, notes, all links, homepage, print');
}finally{await browser.close()}
})().catch(e=>{console.error(e);process.exitCode=1});
+17
View File
@@ -0,0 +1,17 @@
import {mkdir,copyFile,writeFile} from 'node:fs/promises';
import {slides} from '../presentations/less-prompting-more-shipping/slides.mjs';
const root='presentations/less-prompting-more-shipping';
const out=`static/${root}`;
await mkdir(`${out}/assets`,{recursive:true});
for(const [src,dest] of [['dist/reveal.js','reveal.js'],['dist/reveal.css','reveal.css'],['plugin/notes/notes.js','notes.js'],['LICENSE','REVEAL-LICENSE']]) await copyFile(`node_modules/reveal.js/${src}`,`${out}/assets/${dest}`);
for(const weight of [400,700,900]) await copyFile(`node_modules/@fontsource/lato/files/lato-latin-${weight}-normal.woff2`,`${out}/assets/lato-latin-${weight}-normal.woff2`);
await copyFile('node_modules/@fontsource/lato/LICENSE',`${out}/assets/LATO-LICENSE`);
await copyFile(`${root}/theme.css`,`${out}/theme.css`);
const esc=s=>s.replaceAll('&','&amp;').replaceAll('<','&lt;').replaceAll('>','&gt;').replaceAll('"','&quot;');
await writeFile(`${out}/index.html`,`<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta name="description" content="The AI Architect’s First Six Months: a proposed, evidence-led strategy."><title>Less Prompting, More Shipping | Eldorado</title><link rel="icon" href="data:,"><link rel="stylesheet" href="./assets/reveal.css"><link rel="stylesheet" href="./theme.css"></head><body>
<nav class="toolbar" aria-label="Presentation tools"><a href="../../">Portal</a><a href="../../strategy/target-operating-model/">Strategy</a><button id="overview" type="button">Overview</button><button id="notes" type="button">Notes</button><button id="fullscreen" type="button">Fullscreen</button><a href="?print-pdf">Print</a><span class="help">← → navigate · Esc overview · S notes · F fullscreen</span></nav>
<div class="reveal deck"><div class="slides">${slides.map((s,i)=>`<section id="${s.id}" class="${s.kind}" data-timing="${s.minutes*60}" ${s.kind==='dark'?'data-background-color="#1f1f29"':''}><p class="eyebrow">ELDORADO / ${i===17?'DISCUSSION':i===18?'REFERENCE':'AI STRATEGY'}</p><${i?'h2':'h1'}>${s.title}</${i?'h2':'h1'}>${s.body}<aside class="notes"><p><strong>${s.minutes} minutes.</strong> ${esc(s.notes)}</p><p>Sources: ${esc(s.sources.join('; '))}.</p></aside></section>`).join('\n')}</div></div>
<script src="./assets/reveal.js"></script><script src="./assets/notes.js"></script><script src="./runtime.js"></script></body></html>`);
await copyFile(`${root}/runtime.js`,`${out}/runtime.js`);
await writeFile(`${out}/source-map.json`,JSON.stringify({version:'1.0.0',strategyRevision:'fcaa84f3bf9058f4ede0a097d16d0e3571590a6b',assignment:'AI_Architect_Test_Task.pdf',talkMinutes:slides.slice(0,17).reduce((a,s)=>a+s.minutes,0),discussionMinutes:15,slides:slides.map(({id,title,minutes,sources},i)=>({number:i+1,id,title,minutes,sources}))},null,2));
console.log(`Built ${slides.length} local Reveal.js slides at ${out}`);
+27
View File
@@ -0,0 +1,27 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import {readFileSync} from 'node:fs';
import {execFileSync} from 'node:child_process';
import {slides} from '../presentations/less-prompting-more-shipping/slides.mjs';
const read=p=>readFileSync(p,'utf8');
test('deck preserves assignment coverage, 35+15 timing and traceable notes',()=>{
assert.equal(slides.length,19); assert.equal(new Set(slides.map(s=>s.id)).size,19);
assert.equal(slides.slice(0,17).reduce((n,s)=>n+s.minutes,0),35);
assert.equal(slides[17].minutes,15); assert.equal(slides[18].minutes,0);
for(const s of slides){assert(s.sources.length);assert(s.notes.length>250);assert(s.body.length);}
for(const id of ['months-one-two','months-three-four','months-five-six','measures','month-six','beyond']) assert(slides.some(s=>s.id===id));
for(const id of ['experiment','inconclusive','stop']) assert.match(slides.find(s=>s.id===id).body,/fictional/i);
assert.match(slides.find(s=>s.id==='qualification').notes,/separately authorizes/);
});
test('build emits local pinned Reveal assets, stable IDs and no production CDN',()=>{
execFileSync(process.execPath,['scripts/build-presentations.mjs']);
const dir='static/presentations/less-prompting-more-shipping'; const html=read(`${dir}/index.html`);
assert.equal((html.match(/<section /g)||[]).length,19);
assert.equal((html.match(/class="notes"/g)||[]).length,19);
assert(!/https?:\/\//.test(html));
assert.match(html,/href="\.\.\/\.\.\/strategy\/target-operating-model\/"/);
assert.match(read(`${dir}/assets/reveal.js`),/5\.2\.1/);
assert.match(read(`${dir}/runtime.js`),/hash:true/);
assert.equal(JSON.parse(read('package.json')).dependencies['reveal.js'],'5.2.1');
assert.match(read('src/pages/index.tsx'),/Open presentation/);
});
+6
View File
@@ -87,6 +87,12 @@ export async function verifySidebarCoverage() {
assert(body.includes('window.location.replace') && body.includes('name="robots" content="noindex"') && !body.includes('<article'), 'Metric alias must remain a non-indexed redirect');
assert.equal(body, readFileSync(join('static',file),'utf8'));
reason = 'Legacy measurement-contracts hash-aware redirect to canonical Measurement pages';
} else if (file === 'presentations/less-prompting-more-shipping/index.html') {
const body = readFileSync(join('build',file),'utf8');
assert.equal(body, readFileSync(join('static',file),'utf8'));
assert(body.includes('class="reveal deck"') && body.includes('class="notes"'));
assert(readFileSync('build/index.html','utf8').includes('./presentations/less-prompting-more-shipping/'));
reason = 'Reveal presentation linked from the homepage, not a competing documentation page';
} else if (file.startsWith('diagrams/') && readFileSync(join('build',file),'utf8').includes('<svg')) {
assert.equal(readFileSync(join('build',file),'utf8'), readFileSync(join('static',file),'utf8'), `Viewer must match generated static asset: ${file}`);
reason = 'Standalone diagram viewer asset, not documentation';