Merge remote-tracking branch 'origin/main' into docs/structure-department-pages
Build and publish Eldorado documentation / build (pull_request) Successful in 49s
Build and publish Eldorado documentation / build (pull_request) Successful in 49s
This commit is contained in:
commit
9bacb7043a
53 files changed
+974
-390
No files matched your search
@@ -0,0 +1,61 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import {readFileSync} from 'node:fs';
|
||||
import {targetCaption} from './target-caption.mjs';
|
||||
const read = p => readFileSync(p,'utf8');
|
||||
const matrix = JSON.parse(read('research/ai-adoption/assignment-evidence.json'));
|
||||
test('PDF assignment and audit matrix map to actual page evidence',()=>{
|
||||
assert.deepEqual(matrix.requirements.map(r=>r.id),['format','context','q1','q2','q3','q4','assumptions','F1','F2-F3']);
|
||||
for(const row of matrix.requirements){
|
||||
const text=row.pages.map(p=>read(`docs/strategy/${p}.mdx`)).join('\n').toLowerCase();
|
||||
for(const marker of row.markers) assert(text.includes(marker.toLowerCase()),`${row.id}: ${marker}`);
|
||||
}
|
||||
for(const p of matrix.verification_sources)assert(read(p));
|
||||
assert.equal(35+15,50);
|
||||
});
|
||||
test('main reading path does not promise skills rollout or autonomous destination',()=>{
|
||||
const main=read('docs/strategy/target-operating-model.mdx').split('<details>')[0];
|
||||
assert(main.indexOf('strategy-six-months')<main.indexOf('Choose the smallest intervention'));
|
||||
for(const stale of ['The first investment is a governed skills system','An autonomous agent executes and governs every phase','foundation for Stage 2'])assert(!main.includes(stale));
|
||||
const roadmap=read('docs/strategy/roadmap.mdx');
|
||||
for(const month of [1,2,3,4,5,6]){
|
||||
const block=roadmap.split(`**Month ${month} — `)[1].split('\n\n')[0];
|
||||
for(const word of ['Output:','Dependency:'])assert(block.includes(word),`${month}: ${word}`);
|
||||
}
|
||||
});
|
||||
test('proposed diagram preserves months and explicit mobile feedback legend',()=>{
|
||||
const pair=['','-mobile'].map(s=>JSON.parse(read(`diagrams/strategy-six-months${s}.architecture.json`)));
|
||||
const nodes=d=>d.components.map(({pos,size,...n})=>n);
|
||||
assert.deepEqual(nodes(pair[0]),nodes(pair[1]));
|
||||
for(const d of pair){
|
||||
for(let m=1;m<=6;m++)assert(d.components.some(n=>n.label.startsWith(`Month ${m}:`)));
|
||||
const legend=d.components.find(n=>n.id==='reference');
|
||||
assert(legend.sublabel.includes('Month 6 → 2:'));assert(legend.sublabel.includes('Month 5 → 1:'));
|
||||
assert(legend.sublabel.includes('not company commitments'));
|
||||
}
|
||||
});
|
||||
test('first-revision recovery and diagram decisions cannot disappear',()=>{
|
||||
for(const id of ['skills-quality','skills-governance','skills-maintenance']){
|
||||
const text=read(`docs/strategy/${id}.mdx`);
|
||||
for(const term of ['N/A','pinned clean','current'])assert(text.includes(term),id);
|
||||
}
|
||||
for(const s of ['','-mobile']){
|
||||
const g=JSON.parse(read(`diagrams/strategy-governance${s}.architecture.json`));
|
||||
assert(g.components.find(n=>n.id==='canary').sublabel.includes('pilot'));
|
||||
assert(g.components.find(n=>n.id==='approve').sublabel.includes('approve / hold'));
|
||||
const a=JSON.parse(read(`diagrams/strategy-adoption${s}.architecture.json`));
|
||||
assert(a.components.find(n=>n.id==='expand').sublabel.includes('Retain / improve / hold / stop'));
|
||||
}
|
||||
const mobile=JSON.parse(read('diagrams/strategy-maintenance-mobile.architecture.json'));
|
||||
const order=[...mobile.components].sort((a,b)=>a.pos[1]-b.pos[1]).map(n=>n.id);
|
||||
assert.deepEqual(order.slice(0,2),['observe','triage']);
|
||||
});
|
||||
test('target caption is inside SVG, expands canvas and fails closed',()=>{
|
||||
const svg='<svg role="img" viewBox="0 0 1200 1000"><g data-node-id="agent"/></svg>';
|
||||
const out=targetCaption(svg);
|
||||
for(const term of ['Proposed optional target','Solid: lifecycle','Dotted: execution','Dashed: human-controlled'])assert(out.includes(term));
|
||||
assert(out.includes('viewBox="0 0 1200 1130"'));
|
||||
assert(out.indexOf('eldorado-target-caption')<out.indexOf('</svg>'));
|
||||
assert.equal((out.match(/data-node-id=/g)||[]).length,1);
|
||||
assert.throws(()=>targetCaption('<svg/>'));
|
||||
});
|
||||
@@ -12,10 +12,19 @@ const fs = require('node:fs');
|
||||
const context = await browser.newContext({viewport: {width, height: 1000}, colorScheme: 'light'});
|
||||
const page = await context.newPage();
|
||||
await page.goto(`${base}/eldorado-v1/documentation/strategy/`, {waitUntil: 'networkidle'});
|
||||
const panel = page.locator('.archify-panel--story');
|
||||
await page.addStyleTag({content:'html {scroll-behavior:auto !important;}'});
|
||||
await page.getByText('Conditional long-term Target operating model — not the six-month promise',{exact:true}).focus();
|
||||
await page.keyboard.press('Enter');
|
||||
const panel = page.locator('.archify-panel--story[data-diagram="strategy-target"]');
|
||||
const iframe = panel.locator(width > 700 ? '.story-desktop' : '.story-mobile');
|
||||
await iframe.scrollIntoViewIfNeeded();
|
||||
const frame = await (await iframe.elementHandle()).contentFrame();
|
||||
const showControls = () => iframe.evaluate(n => {
|
||||
scrollTo({top:n.getBoundingClientRect().top+scrollY-120,behavior:'instant'});
|
||||
return new Promise(resolve=>requestAnimationFrame(()=>requestAnimationFrame(resolve)));
|
||||
});
|
||||
await showControls();
|
||||
const activate = async selector => {await frame.locator(selector).focus(); await page.keyboard.press('Enter');};
|
||||
const theme = async value => {
|
||||
await page.waitForFunction(t => document.documentElement.dataset.theme === t, value);
|
||||
await frame.waitForFunction(t => document.documentElement.dataset.theme === t, value);
|
||||
@@ -41,9 +50,9 @@ const fs = require('node:fs');
|
||||
assert(menu.y + menu.height <= viewport.y + viewport.height + 1, `${selector} clips vertically`);
|
||||
};
|
||||
const light = await frame.locator('body').evaluate(n => getComputedStyle(n).backgroundColor);
|
||||
await frame.locator('#btn-theme').click(); await theme('dark');
|
||||
await activate('#btn-theme'); await theme('dark');
|
||||
assert.notEqual(await frame.locator('body').evaluate(n => getComputedStyle(n).backgroundColor), light);
|
||||
await frame.locator('#btn-theme').click(); await theme('light');
|
||||
await activate('#btn-theme'); await theme('light');
|
||||
// Mobile portal theme control lives in the navigation drawer.
|
||||
const portalTheme = async target => {
|
||||
if (width <= 700) await page.getByRole('button', {name: 'Toggle navigation bar'}).click();
|
||||
@@ -52,33 +61,34 @@ const fs = require('node:fs');
|
||||
if (await page.locator('html').getAttribute('data-theme') === target) break;
|
||||
}
|
||||
if (width <= 700) await page.getByRole('button', {name: 'Close navigation bar'}).click();
|
||||
await showControls();
|
||||
};
|
||||
await portalTheme('dark'); await theme('dark');
|
||||
await portalTheme('light'); await theme('light');
|
||||
for (const preset of ['signal-flow', 'blueprint', 'editorial', 'classic']) {
|
||||
await frame.locator('#btn-preset').click();
|
||||
await activate('#btn-preset');
|
||||
await checkMenu('.preset-menu');
|
||||
await frame.locator(`[data-preset-value="${preset}"]`).click();
|
||||
await activate(`[data-preset-value="${preset}"]`);
|
||||
assert.equal(await frame.locator('html').getAttribute('data-preset'), preset);
|
||||
}
|
||||
// A theme change must not reload the iframe or lose the selected visual preset.
|
||||
await frame.locator('#btn-preset').click();
|
||||
await frame.locator('[data-preset-value="blueprint"]').click();
|
||||
await activate('#btn-preset');
|
||||
await activate('[data-preset-value="blueprint"]');
|
||||
await portalTheme('dark'); await theme('dark');
|
||||
assert.equal(await frame.locator('html').getAttribute('data-preset'), 'blueprint');
|
||||
const exports = [];
|
||||
for (const format of ['png', 'jpeg', 'webp', 'svg', 'svg-light', 'svg-dark', 'webm']) {
|
||||
await frame.locator('#btn-export').click();
|
||||
await activate('#btn-export');
|
||||
await checkMenu('.export-menu');
|
||||
if (format === 'webm' && await frame.locator('[data-format="webm"]').isDisabled()) {
|
||||
exports.push({format, unsupported: true});
|
||||
await frame.locator('#btn-export').click();
|
||||
await activate('#btn-export');
|
||||
continue;
|
||||
}
|
||||
console.log(`Export ${width} ${format}`);
|
||||
const [download] = await Promise.all([
|
||||
page.waitForEvent('download', {timeout: 60000}),
|
||||
frame.locator(`[data-format="${format}"]`).click(),
|
||||
activate(`[data-format="${format}"]`),
|
||||
]);
|
||||
assert.equal(await download.failure(), null);
|
||||
const path = `${output}/${width}-${format}-${download.suggestedFilename()}`;
|
||||
@@ -123,14 +133,16 @@ const fs = require('node:fs');
|
||||
await page.evaluate(() => scrollTo({top: 0, behavior: 'instant'}));
|
||||
await page.evaluate(() => new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve))));
|
||||
await page.screenshot({path:`${output}/${width}.png`, fullPage:true});
|
||||
await frame.locator('#btn-theme').click(); await theme('dark');
|
||||
await frame.locator('#btn-theme').focus(); await page.keyboard.press('Enter'); await theme('dark');
|
||||
// Exercise keyboard navigation without racing the portal's smooth scroll.
|
||||
await page.locator('.strategy-content a[href$="/strategy/roadmap/"]').filter({visible:true}).first().focus();
|
||||
await page.keyboard.press('Enter');
|
||||
await page.waitForURL('**/strategy/roadmap/');
|
||||
await page.goBack({waitUntil:'domcontentloaded'});
|
||||
await page.waitForURL('**/strategy/target-operating-model/');
|
||||
await page.getByRole('heading', {name:'Target operating model', exact:true, level:1}).waitFor();
|
||||
await page.getByRole('heading', {name:'First six months as AI Architect', exact:true, level:1}).waitFor();
|
||||
await page.getByText('Conditional long-term Target operating model — not the six-month promise',{exact:true}).focus();
|
||||
await page.keyboard.press('Enter');
|
||||
const next = page.locator(`.archify-panel--story[data-diagram="strategy-target"] > .story-${width > 700 ? 'desktop' : 'mobile'}`);
|
||||
await next.scrollIntoViewIfNeeded();
|
||||
await (await (await next.elementHandle()).contentFrame()).waitForFunction(() => document.documentElement.dataset.theme === 'dark');
|
||||
|
||||
@@ -26,7 +26,7 @@ const views = {
|
||||
const ids=views[name];
|
||||
geometry.cards.sort((a,b)=>ids.indexOf(a.id)-ids.indexOf(b.id));
|
||||
assert.deepEqual(geometry.cards.map(n => n.id), ids);
|
||||
assert.deepEqual(geometry.cards.map(n => n.number), ids.length === 6 ? ids.map((_,i)=>`Step ${i+1}`) : name === 'strategy-roadmap' ? ['Stage 1','Stage 2'] : ['First','Then']);
|
||||
assert.deepEqual(geometry.cards.map(n => n.number), ids.length === 6 ? ids.map((_,i)=>`Step ${i+1}`) : name === 'strategy-roadmap' ? ['Proposed M1–4','Proposed M5–6'] : ['First','If justified']);
|
||||
assert.equal(geometry.edges.length,ids.length===6?6:1);
|
||||
if(ids.length===6) assert(geometry.edges.some(e=>e.id==='repeat'));
|
||||
for(const edge of geometry.edges) assert(edge.box.left>=0 && edge.box.right<=geometry.width+1 && edge.box.top>=0 && edge.box.bottom<=geometry.height+1, `clipped edge ${name} ${edge.id}`);
|
||||
@@ -60,7 +60,9 @@ const views = {
|
||||
for(const theme of ['light','dark']) {
|
||||
const frameTop=await iframe.evaluate(n=>n.getBoundingClientRect().top+scrollY);
|
||||
await page.evaluate(y=>scrollTo({top:y-120,behavior:'instant'}),frameTop);
|
||||
if(await frame.locator('html').getAttribute('data-theme')!==theme) await frame.locator('#btn-theme').click();
|
||||
if(await frame.locator('html').getAttribute('data-theme')!==theme) {
|
||||
await frame.locator('#btn-theme').focus(); await page.keyboard.press('Enter');
|
||||
}
|
||||
await page.waitForFunction(t=>document.documentElement.dataset.theme===t,theme);
|
||||
const inline=await inspect(frame,name,width<1100);
|
||||
await frame.locator('#btn-preset').focus();await page.keyboard.press('Enter');
|
||||
|
||||
@@ -61,14 +61,14 @@ const fs = require('node:fs');
|
||||
assert.equal(response.status(), 200);
|
||||
assert.equal(await page.locator('h1').innerText(), 'Roadmap');
|
||||
const headings = page.locator('.strategy-content > h2');
|
||||
assert.deepEqual((await headings.allTextContents()).map(s => s.replace(/\u200b/g, '')), ['Two stages toward Target','Stage 1: Delivery first','Improve within each phase','Delivery in practice']);
|
||||
assert.deepEqual((await headings.allTextContents()).map(s => s.replace(/\u200b/g, '')), ['Six months, two investment decisions','Delivery first, replication only if earned','Keep one learning loop','Solo capacity and influence']);
|
||||
const text = await page.locator('.strategy-content').innerText();
|
||||
for (const marker of ['Humans direct, guide and review throughout', 'Delivery keeps improving', 'system-logic source', 'baseline', 'Target operating model']) assert(text.includes(marker), marker);
|
||||
assert(!text.includes('Supporting gate evidence and hold conditions\n\nThese are'));
|
||||
if (width < 700) await page.getByRole('button', {name:'Toggle navigation bar'}).click();
|
||||
const menu = page.locator('.theme-doc-sidebar-menu .menu__link').filter({visible:true});
|
||||
await menu.first().waitFor({state:'visible'});
|
||||
assert.deepEqual((await menu.allTextContents()).slice(0,2), ['Target','Roadmap']);
|
||||
assert.deepEqual((await menu.allTextContents()).slice(0,2), ['Six-month strategy','Roadmap']);
|
||||
if (width < 700) await page.getByRole('button', {name:'Close navigation bar'}).click();
|
||||
const panel = page.locator('.archify-panel--story').first();
|
||||
const iframe = panel.locator(width > 700 ? '.story-desktop' : '.story-mobile');
|
||||
@@ -125,7 +125,7 @@ const fs = require('node:fs');
|
||||
const [download] = await Promise.all([page.waitForEvent('download'), frame.locator('[data-format="svg"]').click()]);
|
||||
assert.equal(await download.failure(), null);
|
||||
await download.saveAs(`${output}/${width}.svg`);
|
||||
assert(fs.readFileSync(`${output}/${width}.svg`, 'utf8').includes('Adopt skills'));
|
||||
assert(fs.readFileSync(`${output}/${width}.svg`, 'utf8').includes('Learn and test'));
|
||||
assert(fs.readFileSync(`${output}/${width}.svg`, 'utf8').includes('eldorado-roadmap-typography'));
|
||||
for (const id of ['skills','autonomy']) {
|
||||
await frame.locator(`svg[role="img"] [data-node-id="${id}"]`).screenshot({path:`${output}/${width}-${id}-card.png`});
|
||||
|
||||
@@ -23,17 +23,19 @@ const fs = require('node:fs');
|
||||
if (width < 700) await page.getByRole('button', {name:'Toggle navigation bar'}).click();
|
||||
const menu = page.locator('.theme-doc-sidebar-menu .menu__link').filter({visible:true});
|
||||
await menu.first().waitFor({state:'visible'});
|
||||
assert.deepEqual(await menu.allTextContents(), ['Target', 'Roadmap', 'Stage - 01 - Skills Adoption', 'Stage - 02 - Autonomous Agent']);
|
||||
assert.deepEqual(await menu.allTextContents(), ['Six-month strategy', 'Roadmap', 'One bounded experiment', 'Beyond six months']);
|
||||
assert((await menu.nth(0).getAttribute('href')).endsWith('/strategy/target-operating-model/'));
|
||||
assert((await menu.nth(1).getAttribute('href')).endsWith('/strategy/roadmap/'));
|
||||
assert.equal(await menu.filter({hasText:/^Target$/}).count(), 1);
|
||||
assert.equal(await menu.filter({hasText:/^Six-month strategy$/}).count(), 1);
|
||||
await page.screenshot({path:`${output}/${width}-${route ? 'target' : 'landing'}-navigation.png`,fullPage:true,animations:'disabled'});
|
||||
await menu.nth(1).click();
|
||||
await page.waitForURL('**/strategy/roadmap/');
|
||||
await page.getByRole('heading', {level:1,name:'Roadmap',exact:true}).waitFor();
|
||||
await page.goBack({waitUntil:'domcontentloaded'});
|
||||
await page.waitForURL('**/strategy/target-operating-model/');
|
||||
const iframe = page.locator(`.archify-panel--story > .story-${width > 700 ? 'desktop' : 'mobile'}`);
|
||||
await page.getByText('Conditional long-term Target operating model — not the six-month promise',{exact:true}).focus();
|
||||
await page.keyboard.press('Enter');
|
||||
const iframe = page.locator(`[data-diagram="strategy-target"] > .story-${width > 700 ? 'desktop' : 'mobile'}`);
|
||||
await iframe.scrollIntoViewIfNeeded();
|
||||
const frame = await (await iframe.elementHandle()).contentFrame();
|
||||
await frame.locator('svg[role="img"]').waitFor();
|
||||
@@ -77,8 +79,9 @@ const fs = require('node:fs');
|
||||
}
|
||||
assert(metrics.labels.every(n=>n.px>=14), JSON.stringify(metrics.labels));
|
||||
assert.deepEqual(metrics.overflow, []);
|
||||
await frame.locator('#btn-export').click();
|
||||
const [download] = await Promise.all([page.waitForEvent('download'), frame.locator('[data-format="svg"]').click()]);
|
||||
await frame.locator('#btn-export').focus(); await page.keyboard.press('Enter');
|
||||
await frame.locator('[data-format="svg"]').focus();
|
||||
const [download] = await Promise.all([page.waitForEvent('download'), page.keyboard.press('Enter')]);
|
||||
assert.equal(await download.failure(), null);
|
||||
const exportPath = `${output}/${width}-${route ? 'target' : 'landing'}.svg`;
|
||||
await download.saveAs(exportPath);
|
||||
|
||||
@@ -46,7 +46,7 @@ const fs = require('node:fs');
|
||||
await page.waitForURL(`${base}/eldorado-v1/documentation/strategy/measurement/supported-bundle-coverage/`);
|
||||
await page.getByRole('heading', {level:1, name:'Supported bundle coverage', exact:true}).waitFor();
|
||||
assert((await page.locator('article').innerText()).includes('Supported bundle coverage'));
|
||||
assert.equal(await page.locator('.breadcrumbs a').filter({hasText:'Stage - 01 - Skills Adoption'}).getAttribute('href'), '/eldorado-v1/documentation/strategy/stage-01-skills-adoption/');
|
||||
assert.equal(await page.locator('.breadcrumbs a').filter({hasText:'One bounded experiment'}).getAttribute('href'), '/eldorado-v1/documentation/strategy/stage-01-skills-adoption/');
|
||||
assert((await page.locator('.breadcrumbs').innerText()).includes('Measurement'));
|
||||
await open();
|
||||
await menu.getByRole('link', {name:'Supported bundle coverage', exact:true}).waitFor({state:'visible'});
|
||||
|
||||
@@ -15,8 +15,8 @@ const fs = require('node:fs');
|
||||
page.on('pageerror', error => errors.push(error.message));
|
||||
const response = await page.goto(`${base}/eldorado-v1/documentation/strategy/measurement/`, {waitUntil:'networkidle'});
|
||||
assert.equal(response.status(), 200);
|
||||
assert.equal(await page.locator('h1').innerText(), 'Skills Measurement');
|
||||
assert((await page.title()).startsWith('Skills Measurement |'));
|
||||
assert.equal(await page.locator('h1').innerText(), 'Measuring real output');
|
||||
assert((await page.title()).startsWith('Measuring real output |'));
|
||||
assert.equal(await page.locator('.strategy-content details').count(), 1);
|
||||
assert.equal(await page.locator('.strategy-content details[open]').count(), 0);
|
||||
assert((await page.locator('.strategy-content details summary').innerText()).includes('Implementation status'));
|
||||
@@ -28,7 +28,7 @@ const fs = require('node:fs');
|
||||
const menu = page.locator('.theme-doc-sidebar-menu .menu__link').filter({visible:true});
|
||||
await menu.first().waitFor({state:'visible'});
|
||||
const metrics = JSON.parse(fs.readFileSync('research/ai-adoption/measurement-migration.json','utf8')).metrics;
|
||||
assert.deepEqual(await menu.allTextContents(), ['Target','Roadmap','Stage - 01 - Skills Adoption','Measurement',...metrics.map(m => m.title),'Collection and safeguards','Run the synthetic measurement example','Baseline','Governance','Quality','Adoption','Maintenance','Stage - 02 - Autonomous Agent']);
|
||||
assert.deepEqual(await menu.allTextContents(), ['Six-month strategy','Roadmap','One bounded experiment','Measurement',...metrics.map(m => m.title),'Collection and safeguards','Run the synthetic measurement example','Baseline','Governance','Quality','Adoption','Maintenance','Beyond six months']);
|
||||
if (width < 700) await page.getByRole('button', {name:'Close navigation bar'}).click();
|
||||
const panel = page.locator('.archify-panel--story');
|
||||
const iframe = panel.locator(width > 700 ? '.story-desktop' : '.story-mobile');
|
||||
|
||||
@@ -6,10 +6,10 @@ const assert=require('node:assert/strict');
|
||||
const out=`.cache/skills-${process.argv[3]||'candidate'}`;fs.mkdirSync(out,{recursive:true});
|
||||
const browser=await chromium.launch({executablePath:process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE,args:['--no-sandbox']}); const results=[];
|
||||
try {
|
||||
for(const topic of ['measurement','baseline','governance','quality','adoption','maintenance','stage01','stage02']) for(const width of [1440,1024,390,320]) {
|
||||
for(const topic of ['six-months','measurement','baseline','governance','quality','adoption','maintenance','stage01','stage02']) for(const width of [1440,1024,390,320]) {
|
||||
console.log(`Checking ${topic} at ${width}px`);
|
||||
const page=await browser.newPage({viewport:{width,height:900},colorScheme:'light'});const errors=[];page.on('pageerror',e=>errors.push(e.message));
|
||||
const route=topic==='stage01'?'stage-01-skills-adoption':topic==='stage02'?'stage-02-autonomous-agent':['measurement','baseline'].includes(topic)?topic:`skills-${topic}`;
|
||||
const route=topic==='six-months'?'target-operating-model':topic==='stage01'?'stage-01-skills-adoption':topic==='stage02'?'stage-02-autonomous-agent':['measurement','baseline'].includes(topic)?topic:`skills-${topic}`;
|
||||
assert.equal((await page.goto(`${base}/eldorado-v1/documentation/strategy/${route}/`,{waitUntil:'networkidle'})).status(),200);
|
||||
const panel=page.locator('.archify-panel--skills');assert.equal(await panel.count(),1);
|
||||
const iframe=panel.locator(width>1100?'.story-desktop':'.story-mobile');await iframe.scrollIntoViewIfNeeded();
|
||||
|
||||
@@ -6,14 +6,14 @@ const fs = require('node:fs');
|
||||
const output = `.cache/strategy-navigation-${process.argv[3] || 'local'}`;
|
||||
fs.mkdirSync(output, {recursive:true});
|
||||
const prefix = '/eldorado-v1/documentation/strategy/';
|
||||
const first = 'Stage - 01 - Skills Adoption';
|
||||
const second = 'Stage - 02 - Autonomous Agent';
|
||||
const first = 'One bounded experiment';
|
||||
const second = 'Beyond six months';
|
||||
const topics = ['Measurement','Baseline','Governance','Quality','Adoption','Maintenance'];
|
||||
const metrics = JSON.parse(fs.readFileSync('research/ai-adoption/measurement-migration.json','utf8')).metrics;
|
||||
const childIds = [...metrics.map(m => m.docId),'measurement/collection-and-safeguards','measurement/synthetic-example'];
|
||||
const childTitles = [...metrics.map(m => m.title),'Collection and safeguards','Run the synthetic measurement example'];
|
||||
const ids = ['target-operating-model','roadmap','stage-01-skills-adoption','measurement',...childIds,'baseline','skills-governance','skills-quality','skills-adoption','skills-maintenance','stage-02-autonomous-agent'];
|
||||
const titles = ['Target','Roadmap',first,'Skills Measurement',...childTitles,'Baseline','Skills governance','Skills quality','Skills adoption','Skills maintenance',second];
|
||||
const titles = ['First six months as AI Architect','Roadmap',first,'Measuring real output',...childTitles,'Baseline','Skills governance','Skills quality','Skills adoption','Skills maintenance',second];
|
||||
const browser = await chromium.launch({headless:true, executablePath:process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE, args:['--no-sandbox']});
|
||||
const results = [];
|
||||
try {
|
||||
@@ -35,24 +35,24 @@ const fs = require('node:fs');
|
||||
assert.equal((await page.goto(`${base}${prefix}roadmap/`, {waitUntil:'networkidle'})).status(), 200);
|
||||
let menu = await openMenu();
|
||||
const visibleLabels = () => menu.locator('.menu__link').filter({visible:true}).allTextContents();
|
||||
assert.deepEqual(await visibleLabels(), ['Target','Roadmap',first,second]);
|
||||
assert.deepEqual(await visibleLabels(), ['Six-month strategy','Roadmap',first,second]);
|
||||
const category = menu.locator('.theme-doc-sidebar-item-category').filter({has:page.getByRole('link', {name:first, exact:true})});
|
||||
const caret = category.locator(':scope > .menu__list-item-collapsible > button.menu__caret');
|
||||
await caret.click();
|
||||
await category.getByRole('link', {name:'Governance', exact:true}).waitFor({state:'visible'});
|
||||
assert.deepEqual(await visibleLabels(), ['Target','Roadmap',first,...topics,second]);
|
||||
assert.deepEqual(await visibleLabels(), ['Six-month strategy','Roadmap',first,...topics,second]);
|
||||
assert.deepEqual(await category.locator('ul .menu__link').filter({visible:true}).allTextContents(), topics);
|
||||
const measurement = category.locator('.theme-doc-sidebar-item-category').filter({has:page.getByRole('link', {name:'Measurement', exact:true})});
|
||||
const reference = measurement.locator(':scope > .menu__list-item-collapsible > button.menu__caret');
|
||||
await reference.focus();
|
||||
await page.keyboard.press('Enter');
|
||||
await category.getByRole('link', {name:'Validated task success', exact:true}).waitFor({state:'visible'});
|
||||
assert.deepEqual(await visibleLabels(), ['Target','Roadmap',first,topics[0],...childTitles,...topics.slice(1),second]);
|
||||
assert.deepEqual(await visibleLabels(), ['Six-month strategy','Roadmap',first,topics[0],...childTitles,...topics.slice(1),second]);
|
||||
await reference.click();
|
||||
await page.screenshot({path:`${output}/${width}-expanded.png`, animations:'disabled'});
|
||||
await caret.click();
|
||||
await category.getByRole('link', {name:'Governance', exact:true}).waitFor({state:'hidden'});
|
||||
assert.deepEqual(await visibleLabels(), ['Target','Roadmap',first,second]);
|
||||
assert.deepEqual(await visibleLabels(), ['Six-month strategy','Roadmap',first,second]);
|
||||
await menu.getByRole('link', {name:first, exact:true}).click();
|
||||
await page.waitForURL(`${base}${prefix}stage-01-skills-adoption/`);
|
||||
await page.getByRole('heading', {level:1, name:first, exact:true}).waitFor();
|
||||
@@ -64,8 +64,7 @@ const fs = require('node:fs');
|
||||
assert.equal((await page.goto(`${base}${prefix}${id}/`, {waitUntil:'networkidle'})).status(), 200);
|
||||
assert.equal(new URL(page.url()).pathname, `${prefix}${id}/`);
|
||||
// Target has a longer canonical page heading but keeps its short navigation label.
|
||||
if (index) assert.equal(await page.locator('h1').innerText(), titles[index]);
|
||||
else assert((await page.locator('h1').innerText()).includes('Target'));
|
||||
assert.equal(await page.locator('h1').innerText(), titles[index]);
|
||||
const prev = page.locator('.pagination-nav__link--prev');
|
||||
const next = page.locator('.pagination-nav__link--next');
|
||||
if (index) assert.equal(await prev.getAttribute('href'), `${prefix}${id === 'baseline' ? 'measurement' : ids[index-1]}/`);
|
||||
@@ -76,7 +75,7 @@ const fs = require('node:fs');
|
||||
assert.equal(await page.locator('.breadcrumbs a').filter({hasText:first}).getAttribute('href'), `${prefix}stage-01-skills-adoption/`);
|
||||
menu = await openMenu();
|
||||
const expectedTopics = id === 'measurement' || id.startsWith('measurement/') ? [topics[0],...childTitles,...topics.slice(1)] : topics;
|
||||
assert.deepEqual(await visibleLabels(), ['Target','Roadmap',first,...expectedTopics,second]);
|
||||
assert.deepEqual(await visibleLabels(), ['Six-month strategy','Roadmap',first,...expectedTopics,second]);
|
||||
if (id.startsWith('measurement/')) assert.equal(await page.locator('.breadcrumbs a').filter({hasText:'Measurement'}).getAttribute('href'), `${prefix}measurement/`);
|
||||
await closeMenu();
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ const fs=require('node:fs');
|
||||
assert.equal(geometry.nodes.length,(source.components||source.nodes).length,id);
|
||||
for(const n of geometry.nodes) assert(n.x>=0 && n.y>=0 && n.right<=geometry.width+1 && n.bottom<=geometry.height+1,`${id} ${width} clipped ${JSON.stringify(n)} / ${geometry.height}`);
|
||||
const data=await page.locator('.strategy-content').evaluate(el=>({words:el.innerText.trim().split(/\s+/).length,overflow:document.documentElement.scrollWidth>innerWidth,openDetails:el.querySelectorAll('details[open]').length}));
|
||||
assert(data.words<=(id==='measurement'?1600:id==='roadmap'?600:550),id);assert(!data.overflow,id);assert.equal(data.openDetails,0,id);
|
||||
assert(data.words<=(id==='measurement'?1900:id==='roadmap'?1150:['index','target-operating-model'].includes(id)?850:750),id);assert(!data.overflow,id);assert.equal(data.openDetails,0,id);
|
||||
await page.evaluate(()=>scrollTo({top:0,behavior:'instant'}));await page.screenshot({path:`.cache/story-${label}/${width}-${id}.png`,fullPage:true});
|
||||
const opener=panel.getByRole('button',{name:/^Expand /});await opener.click();
|
||||
const close=panel.getByRole('button',{name:'Close full-screen diagram',exact:true});await close.waitFor({state:'visible'});
|
||||
|
||||
@@ -131,6 +131,9 @@ function inspectTypography({mobile}) {
|
||||
for (const width of [1440,390,320]) {
|
||||
const page = await browser.newPage({viewport:{width,height:1000}});
|
||||
await page.goto(`${base}/eldorado-v1/documentation/strategy/target-operating-model/`,{waitUntil:'networkidle'});
|
||||
await page.addStyleTag({content:'html {scroll-behavior:auto !important;}'});
|
||||
await page.getByText('Conditional long-term Target operating model — not the six-month promise',{exact:true}).focus();
|
||||
await page.keyboard.press('Enter');
|
||||
const panel = page.locator('[data-diagram="strategy-target"]');
|
||||
const iframe = panel.locator(`:scope > .story-${width > 700 ? 'desktop' : 'mobile'}`);
|
||||
await iframe.scrollIntoViewIfNeeded();
|
||||
@@ -141,10 +144,11 @@ function inspectTypography({mobile}) {
|
||||
await page.screenshot({path:`${output}/${width}-inline.png`,fullPage:true});
|
||||
const exported = [];
|
||||
for (const theme of ['light','dark']) {
|
||||
if (await frame.locator('html').getAttribute('data-theme') !== theme) await frame.locator('#btn-theme').click();
|
||||
if (await frame.locator('html').getAttribute('data-theme') !== theme) {await frame.locator('#btn-theme').focus(); await page.keyboard.press('Enter');}
|
||||
await frame.waitForFunction(t => document.documentElement.dataset.theme === t, theme);
|
||||
await frame.locator('#btn-export').click();
|
||||
const [download] = await Promise.all([page.waitForEvent('download'),frame.locator('[data-format="svg"]').click()]);
|
||||
await frame.locator('#btn-export').focus(); await page.keyboard.press('Enter');
|
||||
await frame.locator('[data-format="svg"]').focus();
|
||||
const [download] = await Promise.all([page.waitForEvent('download'),page.keyboard.press('Enter')]);
|
||||
assert.equal(await download.failure(),null);
|
||||
const path = `${output}/${width}-${theme}.svg`;
|
||||
await download.saveAs(path);
|
||||
@@ -157,6 +161,10 @@ function inspectTypography({mobile}) {
|
||||
const typography = await standalone.evaluate(inspectTypography,{mobile:width < 700});
|
||||
assert.deepEqual(geometry.errors,[]);
|
||||
assert.deepEqual(typography.errors,[]);
|
||||
assert(await standalone.locator('#eldorado-target-caption').evaluate(g=>{
|
||||
const v=g.ownerSVGElement.viewBox.baseVal;
|
||||
return [...g.querySelectorAll('text')].every(t=>{const b=t.getBBox();return b.x>=v.x&&b.y>=v.y&&b.x+b.width<=v.x+v.width&&b.y+b.height<=v.y+v.height;});
|
||||
}), 'Standalone proposed caption must fit the complete canvas');
|
||||
const background = await standalone.locator('rect.c-bg-rect').evaluate(n => getComputedStyle(n).fill);
|
||||
if (exported.length) assert.notEqual(background,exported[0].background,'standalone theme must change');
|
||||
// CDP captures SVG documents without Playwright's HTML-body screenshot wait.
|
||||
@@ -167,7 +175,7 @@ function inspectTypography({mobile}) {
|
||||
exported.push({theme,background,geometry,typography});
|
||||
await standalone.close();
|
||||
}
|
||||
await panel.getByRole('button',{name:/^Expand /}).click();
|
||||
await panel.getByRole('button',{name:/^Expand /}).focus(); await page.keyboard.press('Enter');
|
||||
const full = await (await panel.locator('dialog iframe').elementHandle()).contentFrame();
|
||||
await full.locator('svg[role="img"]').waitFor();
|
||||
// The existing fullscreen viewer always opens the radial desktop source.
|
||||
|
||||
@@ -4,6 +4,7 @@ import {execFileSync} from 'node:child_process';
|
||||
import {skillsViewer, isSkillsStory} from './skills-viewer.mjs';
|
||||
import {roadmapViewer} from './roadmap-viewer.mjs';
|
||||
import {targetRouting} from './target-routing.mjs';
|
||||
import {targetCaption} from './target-caption.mjs';
|
||||
import {createHash} from 'node:crypto';
|
||||
const cli = 'vendor/archify/bin/archify.mjs';
|
||||
const types = new Set(['architecture','workflow','sequence','dataflow','lifecycle']);
|
||||
@@ -43,6 +44,7 @@ for (const file of readdirSync('diagrams').sort()) {
|
||||
// Verify the upstream artifact first, then add the scoped portal integration.
|
||||
let viewer = readFileSync(output, 'utf8');
|
||||
if (name === 'strategy-target') viewer = targetRouting(viewer);
|
||||
if (/^strategy-target(-mobile)?$/.test(name)) viewer = targetCaption(viewer);
|
||||
// Upstream intentionally disables preset selection in gallery embeds. Enable
|
||||
// only those two guards; fail closed if a future vendor update changes them.
|
||||
const start = viewer.indexOf('Archify.preset = (function () {');
|
||||
|
||||
@@ -30,7 +30,7 @@ export function roadmapViewer(html, spec) {
|
||||
return `<text x="${x+24}" y="${y+top}" font-size="${size}" class="${cls}" ${attrs}>${lines.map((line,i) => `<tspan x="${x+24}" dy="${i ? 34 : 0}">${escape(line)}</tspan>`).join('')}</text>`;
|
||||
};
|
||||
if (!node.sublabel || node.tag || /Output:|Exit:|\n/.test(node.sublabel)) throw new Error(`Roadmap content contract drift: ${node.id}`);
|
||||
const number = spec.nodes.length === 6 ? `Step ${index+1}` : spec.nodes[0].id === 'skills' ? `Stage ${index+1}` : index === 0 ? 'First' : 'Then';
|
||||
const number = spec.nodes.length === 6 ? `Step ${index+1}` : spec.nodes[0].id === 'skills' ? ['Proposed M1–4','Proposed M5–6'][index] : index === 0 ? 'First' : 'If justified';
|
||||
const content = text(number, 40, 26, 't-primary', 'data-stage-number="" font-weight="700"')
|
||||
+ text(node.label, 88, 30, 't-primary', 'data-node-label="" font-weight="700"')
|
||||
+ text(node.sublabel, 160, 26, 't-muted');
|
||||
|
||||
@@ -12,8 +12,8 @@ test('Roadmap has three visual levels and preserves human direction and Delivery
|
||||
const page = read('docs/strategy/roadmap.mdx');
|
||||
const visible = page.split('<details>')[0];
|
||||
assert.deepEqual([...visible.matchAll(/<ArchifyDiagram name="([^"]+)"/g)].map(m => m[1]), Object.keys(views));
|
||||
assert.deepEqual([...visible.matchAll(/^## (.+)$/gm)].map(m => m[1]), ['Two stages toward Target', 'Stage 1: Delivery first', 'Improve within each phase', 'Delivery in practice']);
|
||||
for (const marker of ['Humans direct, guide and review throughout', 'standardizes skills', 'foundation for Stage 2', 'future Autonomous Agent', 'not deployed autonomy', 'order is not yet decided', 'Delivery keeps improving', 'not as six global roadmap stages', 'what skills exist, how they relate, when they are used and how they work together', 'codebase structure', 'existing principles', 'system-logic source', 'tests', 'builds and packaging', 'deployments', 'practices, tooling, controls and problems', 'metrics/KPIs and a baseline', 'friction and feedback', './measurement/index.mdx', './target-operating-model.mdx']) assert(visible.includes(marker), marker);
|
||||
assert.deepEqual([...visible.matchAll(/^## (.+)$/gm)].map(m => m[1]), ['Six months, two investment decisions', 'Delivery first, replication only if earned', 'Keep one learning loop', 'Solo capacity and influence']);
|
||||
for (const marker of ['Humans direct, guide and review throughout', 'current', 'AI-assisted practice', 'One active live pilot at a time', 'Month 1', 'Month 2', 'Month 3', 'Month 4', 'Month 5', 'Month 6', 'Delivery keeps improving', 'what skills exist, how they relate, when they are used and how they work together', 'codebase structure', 'existing principles', 'system-logic source', 'tests', 'builds and packaging', 'deployments', 'retain/change/hold/stop', './measurement/index.mdx', './target-operating-model.mdx']) assert(visible.includes(marker), marker);
|
||||
for (const marker of ['revocation','exact-artifact approvals','idempotency','human takeover','sensitive decisions','security findings','reproducible baseline']) assert(page.includes(marker), marker);
|
||||
assert(!/## [1-5]\.|Prepare foundations|Sustain and improve/.test(page));
|
||||
});
|
||||
|
||||
@@ -4,19 +4,19 @@ import {readFileSync} from 'node:fs';
|
||||
const read = p => readFileSync(p, 'utf8');
|
||||
const stages = ['Scope', 'Architecture', 'UI/UX', 'Planning', 'Delivery', 'UAT', 'Security', 'Releases'];
|
||||
|
||||
test('Strategy opens at the canonical autonomous lifecycle', () => {
|
||||
test('Strategy opens at the assignment and retains autonomy only as a conditional reference', () => {
|
||||
assert.match(read('sidebars/strategy.ts'), /strategySidebar:\s*\[\s*'target-operating-model',\s*'roadmap'/);
|
||||
assert.match(read('docs/strategy/target-operating-model.mdx'), /^sidebar_label: Target$/m);
|
||||
assert.match(read('docs/strategy/target-operating-model.mdx'), /^sidebar_label: Six-month strategy$/m);
|
||||
for (const id of ['target-operating-model']) {
|
||||
const page = read(`docs/strategy/${id}.mdx`);
|
||||
const primary = page.split('<details>')[0];
|
||||
assert(primary.includes('name="strategy-target"'));
|
||||
assert(primary.includes('autonomous agent'));
|
||||
assert(primary.includes('direct, guide and review'));
|
||||
assert(primary.includes('Scope → Architecture → UI/UX → Planning → Delivery → UAT → Security → Releases → Scope'));
|
||||
assert(primary.includes('human-controlled AI Agent'));
|
||||
assert(primary.includes('name="strategy-six-months"'));
|
||||
assert(!primary.includes('name="strategy-target"'));
|
||||
assert(page.includes('Conditional long-term Target operating model'));
|
||||
assert(page.includes('Scope → Architecture → UI/UX → Planning → Delivery → UAT → Security → Releases → Scope'));
|
||||
assert(page.includes('human-controlled AI Agent'));
|
||||
assert(!/code[ -]agent/i.test(page));
|
||||
assert(primary.includes('TO-BE'));
|
||||
assert(primary.includes('Proposed strategy'));
|
||||
assert(!/only routes|People authorize|independent gates protect release/.test(primary));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -6,7 +6,7 @@ const read = path => readFileSync(path, 'utf8');
|
||||
|
||||
test('Skills Measurement is a visible optional registry, separate from workflow application', () => {
|
||||
const page = read('docs/strategy/measurement/index.mdx');
|
||||
assert.match(page, /title: Skills Measurement\nsidebar_label: Measurement/);
|
||||
assert.match(page, /title: Measuring real output\nsidebar_label: Measurement/);
|
||||
for (const heading of ['Establish the baseline', 'Measure the pilot', 'Extend collection as adoption grows', 'Keep the collection running']) assert(!page.includes(heading), heading);
|
||||
assert(page.includes('Registry inclusion does not require a metric to be used, collected or baselined'));
|
||||
assert(page.includes('../baseline.mdx'));
|
||||
|
||||
@@ -41,7 +41,7 @@ test('domain contracts retain human authority, honest measures and Delivery-firs
|
||||
const text=t=>JSON.stringify(JSON.parse(read(`diagrams/strategy-${t}.architecture.json`)));
|
||||
assert(text('governance').includes('Author is not sole approver'));
|
||||
assert(text('quality').includes('Critical effect blocks'));
|
||||
assert(text('adoption').includes('Delivery first')&&text('adoption').includes('Later order undecided'));
|
||||
assert(text('adoption').includes('Delivery first')&&text('adoption').includes('Retain / improve / hold / stop'));
|
||||
assert(text('adoption').includes('No quotas or rankings'));
|
||||
for(const marker of ['Workflow effectiveness','Skills effectiveness & health','Quality: artifact fitness + safety','No mandatory collection','Before / after is not causality','Candidate / approved regression'])assert(text('measurement').includes(marker),marker);
|
||||
assert(text('maintenance').includes('No automatic reinstatement'));
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
// Native editable geometry and edges; scoped editorial typography for Stage 01.
|
||||
export const isSkillsStory = name => /^strategy-(governance|quality|adoption|measurement|baseline|maintenance|stage01|stage02)(-mobile)?$/.test(name);
|
||||
export const isSkillsStory = name => /^strategy-(governance|quality|adoption|measurement|baseline|maintenance|stage01|stage02|six-months)(-mobile)?$/.test(name);
|
||||
const escape = text => text.replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>').replaceAll('"', '"');
|
||||
export function skillsViewer(html, spec) {
|
||||
if (spec.diagram_type !== 'architecture' || !spec.components.length) throw new Error('Skills story contract drift');
|
||||
const stageMobile = /strategy-stage0[12]-mobile\//.test(spec.meta.output);
|
||||
const stageMobile = /strategy-(stage0[12]|six-months)-mobile\//.test(spec.meta.output);
|
||||
const detailSize = stageMobile ? 38 : spec.meta.output.includes('-mobile/') ? 34 : 32;
|
||||
for (const node of spec.components) {
|
||||
const pattern = new RegExp(`<g\\b[^>]*data-node-id="${node.id}"[^>]*>[\\s\\S]*?<text data-node-label=[\\s\\S]*?</g>`);
|
||||
|
||||
@@ -11,7 +11,7 @@ test('Strategy has Target, Roadmap, a linked Skills Adoption category and Autono
|
||||
runInNewContext(compiled, sandbox);
|
||||
const sidebar = JSON.parse(JSON.stringify(sandbox.exports.default.strategySidebar));
|
||||
assert.deepEqual(sidebar, ['target-operating-model', 'roadmap', {
|
||||
type:'category', label:'Stage - 01 - Skills Adoption',
|
||||
type:'category', label:'One bounded experiment',
|
||||
link:{type:'doc', id:'stage-01-skills-adoption'}, collapsed:true,
|
||||
items:[
|
||||
{type:'category', label:'Measurement', collapsed:true, link:{type:'doc', id:'measurement/index'}, items:[
|
||||
@@ -26,7 +26,7 @@ test('Strategy has Target, Roadmap, a linked Skills Adoption category and Autono
|
||||
|
||||
],
|
||||
}, 'stage-02-autonomous-agent']);
|
||||
assert.match(read('docs/strategy/target-operating-model.mdx'), /^sidebar_label: Target$/m);
|
||||
assert.match(read('docs/strategy/target-operating-model.mdx'), /^sidebar_label: Six-month strategy$/m);
|
||||
assert.match(read('docusaurus.config.ts'), /type: 'docSidebar', docsPluginId: 'strategy', sidebarId: 'strategySidebar', label: 'Strategy'/);
|
||||
});
|
||||
|
||||
@@ -34,8 +34,8 @@ test('stage overviews explain the foundation and future without replacing topic
|
||||
const first = read('docs/strategy/stage-01-skills-adoption.mdx');
|
||||
const second = read('docs/strategy/stage-02-autonomous-agent.mdx');
|
||||
for (const [id, title, page] of [
|
||||
['stage-01-skills-adoption', 'Stage - 01 - Skills Adoption', first],
|
||||
['stage-02-autonomous-agent', 'Stage - 02 - Autonomous Agent', second],
|
||||
['stage-01-skills-adoption', 'One bounded experiment', first],
|
||||
['stage-02-autonomous-agent', 'Beyond six months', second],
|
||||
]) {
|
||||
assert(page.includes(`id: ${id}\ntitle: ${title}`));
|
||||
for (const link of ['./roadmap.mdx', './target-operating-model.mdx']) assert(page.includes(link));
|
||||
@@ -43,7 +43,7 @@ test('stage overviews explain the foundation and future without replacing topic
|
||||
for (const id of ['measurement/index','baseline','skills-governance','skills-quality','skills-adoption','skills-maintenance']) assert(first.includes(`./${id}.mdx`));
|
||||
for (const marker of ['human + AI Agent','Delivery first','order is not yet decided','KPIs','baseline','topology','feedback','new current situation']) assert(first.includes(marker), marker);
|
||||
for (const marker of ['future','proven skills','measurements','working practices','direction and review','not deployed autonomy','approved KPI thresholds']) assert(second.includes(marker), marker);
|
||||
assert.match(read('docs/strategy/measurement/index.mdx'), /^title: Skills Measurement\nsidebar_label: Measurement$/m);
|
||||
assert.match(read('docs/strategy/measurement/index.mdx'), /^title: Measuring real output\nsidebar_label: Measurement$/m);
|
||||
});
|
||||
|
||||
test('old Strategy entry redirects to Target instead of maintaining a second document', () => {
|
||||
|
||||
@@ -18,7 +18,8 @@ test('every strategy page leads with a compact diagram story and closes its refe
|
||||
assert(/\*\*(Next|Continue the loop):\*\*/.test(narrative), id);
|
||||
assert(!narrative.includes('| ---'), id);
|
||||
const words = narrative.replace(/<[^>]*>/g, '').trim().split(/\s+/).length;
|
||||
assert(words <= (id === 'measurement' ? 1600 : id === 'roadmap' ? 600 : 500), `${id}: ${words} default narrative words`);
|
||||
const budget = id === 'measurement' ? 1900 : id === 'roadmap' ? 1150 : id === 'target-operating-model' ? 850 : 750;
|
||||
assert(words <= budget, `${id}: ${words} default narrative words`);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -36,7 +37,7 @@ test('mobile story diagrams preserve the same nodes and directed relationships',
|
||||
const desktop = JSON.parse(read(`diagrams/${name}.architecture.json`));
|
||||
const mobile = JSON.parse(read(`diagrams/${name}-mobile.architecture.json`));
|
||||
assert(desktop.components.length >= 5, name);
|
||||
const semantic = d => ({nodes:d.components.map(({pos,size,...n})=>n),edges:d.connections.map(({fromSide,toSide,route,via,labelAt,...e})=>e)});
|
||||
const semantic = d => ({nodes:d.components.map(({pos,size,...n})=>n),edges:d.connections.map(({fromSide,toSide,route,via,labelAt,label,...e})=>e)});
|
||||
assert.deepEqual(semantic(desktop), semantic(mobile), name);
|
||||
assert(desktop.connections.every(e => (e.label || e.id.startsWith('govern-') || /^strategy-(governance|quality|adoption|measurement|maintenance)$/.test(name)) && e.from !== e.to), name);
|
||||
assert.equal(desktop.meta.legend.mode, 'hidden');
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
// Caption the retained optional target without changing its native topology.
|
||||
// Text lives inside the SVG so standalone exports retain proposal/line semantics.
|
||||
export function targetCaption(html) {
|
||||
const roots = [...html.matchAll(/<svg\b[^>]*role="img"[^>]*>/g)];
|
||||
if (roots.length !== 1) throw new Error('Target caption SVG contract drift');
|
||||
const root = roots[0][0];
|
||||
const match = /viewBox="([\d. -]+)"/.exec(root);
|
||||
if (!match) throw new Error('Target caption requires viewBox');
|
||||
const [x,y,w,h] = match[1].trim().split(/\s+/).map(Number);
|
||||
const size = Math.min(26, w / 30);
|
||||
const lines = ['Proposed optional target — not a six-month commitment',
|
||||
'Solid: lifecycle · Dotted: execution / governance', 'Dashed: human-controlled contributions'];
|
||||
const extra = size * 5;
|
||||
const caption = `<g id="eldorado-target-caption" aria-label="Proposed target legend">${lines.map((line,i) => `<text x="${x+20}" y="${y+h+size*(1.2+i*1.3)}" font-family="Arial, sans-serif" font-size="${size}" style="font-size:${size}px!important" class="t-primary">${line}</text>`).join('')}</g>`;
|
||||
return html.replace(root, root.replace(match[0], `viewBox="${x} ${y} ${w} ${h+extra}"`) + caption);
|
||||
}
|
||||
+2
-2
@@ -27,9 +27,9 @@ if (process.argv.includes('--build')) {
|
||||
assert(redirect.includes('/eldorado-v1/documentation/strategy/target-operating-model/'));
|
||||
assert(redirect.includes('window.location.href') && redirect.includes('http-equiv="refresh"') && !redirect.includes('archify-panel'), 'Landing must redirect, not duplicate Target');
|
||||
const strategyPages = [...model.pages,
|
||||
{id:'stage-01-skills-adoption', title:'Stage - 01 - Skills Adoption'},
|
||||
{id:'stage-01-skills-adoption', title:'One bounded experiment'},
|
||||
{id:'baseline', title:'Baseline'},
|
||||
{id:'stage-02-autonomous-agent', title:'Stage - 02 - Autonomous Agent'},
|
||||
{id:'stage-02-autonomous-agent', title:'Beyond six months'},
|
||||
];
|
||||
for (const {id, title} of strategyPages) {
|
||||
const route = `strategy/${id === 'index' ? '' : `${id}/`}index.html`;
|
||||
|
||||
Reference in new issue
Block a user