docs: center Target lifecycle on autonomous agent
Build and publish Eldorado documentation / build (pull_request) Successful in 41s
Build and publish Eldorado documentation / build (pull_request) Successful in 41s
This commit is contained in:
1 parent
504d9f8845
commit
1057433c2a
12 files changed
+186
-67
No files matched your search
@@ -88,7 +88,7 @@ const fs = require('node:fs');
|
||||
if (format.startsWith('svg')) {
|
||||
const valid = await page.evaluate(xml => {
|
||||
const doc = new DOMParser().parseFromString(xml, 'image/svg+xml');
|
||||
return !doc.querySelector('parsererror') && doc.documentElement.localName === 'svg' && doc.querySelectorAll('text').length > 0;
|
||||
return !doc.querySelector('parsererror') && doc.documentElement.localName === 'svg' && doc.querySelectorAll('text').length > 0 && doc.querySelector('#eldorado-cycle-export')?.textContent.includes('stroke-dasharray: 3 9');
|
||||
}, bytes.toString());
|
||||
assert(valid, format);
|
||||
} else {
|
||||
@@ -117,10 +117,13 @@ const fs = require('node:fs');
|
||||
await close.focus(); await page.keyboard.press('Escape');
|
||||
assert(await opener.evaluate(n => n === document.activeElement));
|
||||
await iframe.scrollIntoViewIfNeeded();
|
||||
await page.evaluate(() => scrollTo(0, 0));
|
||||
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 page.locator('.strategy-content a[href$="/strategy/target-operating-model/"]').filter({visible:true}).click();
|
||||
// Exercise keyboard navigation without racing the portal's smooth scroll.
|
||||
await page.locator('.strategy-content a[href$="/strategy/target-operating-model/"]').filter({visible:true}).focus();
|
||||
await page.keyboard.press('Enter');
|
||||
await page.waitForURL('**/strategy/target-operating-model/');
|
||||
await page.getByRole('heading', {name:'Target operating model', exact:true, level:1}).waitFor();
|
||||
const next = page.locator(`.archify-panel--story[data-diagram="strategy-target"] > .story-${width > 700 ? 'desktop' : 'mobile'}`);
|
||||
|
||||
@@ -19,7 +19,7 @@ const fs = require('node:fs');
|
||||
await page.waitForURL('**/strategy/target-operating-model/');
|
||||
for (const route of ['target-operating-model/', '']) {
|
||||
await page.goto(`${base}/eldorado-v1/documentation/strategy/${route}`, {waitUntil:'networkidle'});
|
||||
if (width === 1440) assert.equal(await page.locator('.theme-doc-sidebar-menu .menu__link').first().innerText(), 'Target operating model');
|
||||
if (width === 1440) assert.equal(await page.locator('.theme-doc-sidebar-menu .menu__link').first().innerText(), 'Target');
|
||||
const iframe = page.locator(`.archify-panel--story > .story-${width > 700 ? 'desktop' : 'mobile'}`);
|
||||
await iframe.scrollIntoViewIfNeeded();
|
||||
const frame = await (await iframe.elementHandle()).contentFrame();
|
||||
@@ -32,15 +32,36 @@ const fs = require('node:fs');
|
||||
const box = n.querySelector('rect').getBoundingClientRect();
|
||||
return [...n.querySelectorAll('text')].filter(t => {const b=t.getBoundingClientRect();return b.left<box.left||b.right>box.right||b.top<box.top||b.bottom>box.bottom;}).map(t=>t.textContent);
|
||||
});
|
||||
return {labels,edges,overflow};
|
||||
const relationships = [...svg.querySelectorAll('path[data-edge-id]')].map(n => ({id:n.dataset.edgeId,from:n.dataset.edgeFrom,to:n.dataset.edgeTo,dash:getComputedStyle(n).strokeDasharray,arrow:n.getAttribute('marker-end')}));
|
||||
const nodes = [...svg.querySelectorAll('[data-node-id]')].map(n => {const r=n.querySelector('rect').getBoundingClientRect();return {id:n.dataset.nodeId,x:r.x+r.width/2,y:r.y+r.height/2};});
|
||||
return {labels,edges,overflow,relationships,nodes};
|
||||
});
|
||||
const ids = ['scope','architecture','ui-ux','planning','delivery','uat','releases'];
|
||||
const ids = ['scope','architecture','ui-ux','planning','delivery','uat','security','releases'];
|
||||
assert.deepEqual(metrics.edges.map(e=>e.slice(0,2)), ids.map((id,i)=>[id,ids[(i+1)%ids.length]]));
|
||||
assert(metrics.edges.every(e=>e[2]?.startsWith('url(')), 'all stages must have arrowheads');
|
||||
assert.equal(metrics.nodes.length, 18);
|
||||
const governs = metrics.relationships.filter(e=>e.id.startsWith('govern-'));
|
||||
const contributions = metrics.relationships.filter(e=>e.id.startsWith('contribute-'));
|
||||
assert.deepEqual(governs.map(e=>[e.from,e.to]), ids.map(id=>['agent',id]));
|
||||
assert.deepEqual(contributions.map(e=>[e.from,e.to]), ids.map(id=>[`code-${id}`,id]));
|
||||
assert(governs.every(e=>e.dash==='3px, 9px' && e.arrow?.startsWith('url(')));
|
||||
assert(contributions.every(e=>e.dash==='12px, 8px' && e.arrow?.startsWith('url(')));
|
||||
assert(metrics.relationships.some(e=>e.from==='humans' && e.to==='agent'));
|
||||
if (width > 700) {
|
||||
const agent = metrics.nodes.find(n=>n.id==='agent');
|
||||
const phases = metrics.nodes.filter(n=>ids.includes(n.id));
|
||||
for (const axis of ['x','y']) assert(Math.abs(agent[axis]-(Math.min(...phases.map(n=>n[axis]))+Math.max(...phases.map(n=>n[axis])))/2)<1);
|
||||
for (const id of ids) {
|
||||
const phase=phases.find(n=>n.id===id), code=metrics.nodes.find(n=>n.id===`code-${id}`);
|
||||
assert(Math.hypot(code.x-agent.x,code.y-agent.y)>Math.hypot(phase.x-agent.x,phase.y-agent.y));
|
||||
}
|
||||
}
|
||||
assert(metrics.labels.every(n=>n.px>=14), JSON.stringify(metrics.labels));
|
||||
assert.deepEqual(metrics.overflow, []);
|
||||
assert(!await page.evaluate(()=>document.documentElement.scrollWidth>innerWidth));
|
||||
await page.evaluate(()=>scrollTo(0,0));
|
||||
await page.evaluate(()=>scrollTo({top:0,behavior:'instant'}));
|
||||
await page.waitForFunction(()=>scrollY===0);
|
||||
await page.evaluate(()=>new Promise(resolve=>requestAnimationFrame(()=>requestAnimationFrame(resolve))));
|
||||
await page.screenshot({path:`${output}/${width}-${route ? 'target' : 'landing'}.png`,fullPage:true});
|
||||
results.push({width,route,...metrics});
|
||||
fs.writeFileSync(`${output}/results.json`,JSON.stringify(results,null,2));
|
||||
|
||||
@@ -19,18 +19,21 @@ const fs=require('node:fs');
|
||||
const nodes=[...svg.querySelectorAll('[data-node-id]')].map(n=>{const r=n.getBoundingClientRect();return {id:n.id,x:r.x,y:r.y,right:r.right,bottom:r.bottom};});
|
||||
return {width:innerWidth,height:innerHeight,nodes,svg:svg.getBoundingClientRect().toJSON(),minLabel:Math.min(...[...svg.querySelectorAll('text[data-node-label]')].map(n=>parseFloat(getComputedStyle(n).fontSize)*n.getScreenCTM().a))};
|
||||
});
|
||||
assert(geometry.nodes.length>=5 && geometry.nodes.length<=(['index','target-operating-model'].includes(id)?9:7),id);
|
||||
assert(geometry.nodes.length>=5 && geometry.nodes.length<=(['index','target-operating-model'].includes(id)?18:7),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<=200,id);assert(!data.overflow,id);assert.equal(data.openDetails,0,id);
|
||||
await page.evaluate(()=>scrollTo(0,0));await page.screenshot({path:`.cache/story-${label}/${width}-${id}.png`,fullPage:true});
|
||||
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'});
|
||||
assert.equal(await close.innerText(),'×');assert(await close.evaluate(n=>n===document.activeElement));
|
||||
const box=await close.boundingBox();assert.equal(box.width,44);assert.equal(box.height,44);
|
||||
await close.click();assert(await opener.evaluate(n=>n===document.activeElement));
|
||||
await opener.click();await close.waitFor({state:'visible'});await page.keyboard.press('Escape');await page.waitForFunction(()=>!document.querySelector('.archify-panel--story dialog').open);assert(await opener.evaluate(n=>n===document.activeElement));
|
||||
const summary=page.locator('.strategy-content > details > summary').first();await summary.click();assert(await summary.evaluate(n=>n.parentElement.open));await summary.click();
|
||||
const summary=page.locator('.strategy-content > details > summary').first();
|
||||
await summary.focus();await page.keyboard.press('Enter');
|
||||
await page.waitForFunction(()=>document.querySelector('.strategy-content > details').open);
|
||||
await page.keyboard.press('Enter');
|
||||
results.push({id,width,...data,geometry,modalClick:true,modalEscape:true,focusRestored:true,details:true});fs.writeFileSync(`.cache/story-${label}/results.json`,JSON.stringify(results,null,2));
|
||||
}
|
||||
await page.goto(`${base}/eldorado-v1/documentation/strategy/measurement/#validated-task-success`,{waitUntil:'networkidle'});
|
||||
|
||||
@@ -37,7 +37,15 @@ for (const file of readdirSync('diagrams').sort()) {
|
||||
if (start < 0 || end < start || preset.split(guard).length !== 3) throw new Error('Archify preset integration drift');
|
||||
viewer = viewer.slice(0, start) + preset.replaceAll(guard, '') + viewer.slice(end);
|
||||
if (!viewer.includes('</head>') || !viewer.includes('</body>')) throw new Error(`Invalid viewer: ${output}`);
|
||||
const storyStyles = ['strategy-target', 'strategy-target-mobile'].includes(name) ? cycleStyles : '';
|
||||
const storyStyles = ['strategy-target', 'strategy-target-mobile'].includes(name)
|
||||
? `${name === 'strategy-target' ? ':root {--cycle-label: 27px; --cycle-detail: 21px;}' : ':root {--cycle-label: 38px; --cycle-detail: 28px;}'}${cycleStyles}` : '';
|
||||
if (storyStyles) {
|
||||
// Keep typography and relationship styles inside exported standalone SVGs.
|
||||
const diagramRoot = /<svg\b[^>]*role="img"[^>]*>/g;
|
||||
if ([...viewer.matchAll(diagramRoot)].length !== 1) throw new Error('Cycle SVG integration drift');
|
||||
const exportStyles = storyStyles.replaceAll(':root', 'svg[role="img"]').replaceAll('.diagram-container > svg', 'svg[role="img"]');
|
||||
viewer = viewer.replace(diagramRoot, root => `${root}<style id="eldorado-cycle-export">${exportStyles}</style>`);
|
||||
}
|
||||
writeFileSync(output, viewer.replace('</head>', `<style id="eldorado-inline-controls">${inlineStyles}</style><style id="eldorado-cycle-labels">${storyStyles}</style></head>`)
|
||||
.replace('</body>', `<script>${themeBridge}</script></body>`));
|
||||
renameSync(`static/diagrams/${name}/index.delivery.json`, `.cache/archify/${name}.delivery.json`);
|
||||
|
||||
@@ -2,13 +2,18 @@
|
||||
.diagram-container > svg text {
|
||||
font-family: Arial, sans-serif;
|
||||
letter-spacing: 0;
|
||||
font-size: 18px;
|
||||
font-size: 30px;
|
||||
}
|
||||
.diagram-container > svg text[data-node-label] {
|
||||
font-size: 24px;
|
||||
transform: translateY(-3px);
|
||||
font-size: var(--cycle-label);
|
||||
transform: translateY(-6px);
|
||||
}
|
||||
.diagram-container > svg text[data-detail="context"] {
|
||||
font-size: 18px;
|
||||
transform: translateY(7px);
|
||||
font-size: var(--cycle-detail);
|
||||
transform: translateY(16px);
|
||||
}
|
||||
/* Edge semantics stay in SVG metadata; one page legend avoids repeated labels. */
|
||||
.diagram-container > svg g[data-edge-id] {display: none;}
|
||||
.diagram-container > svg path[data-edge-id^="stage-"] {stroke-width: 2.5;}
|
||||
.diagram-container > svg path[data-edge-id^="govern-"] {stroke-dasharray: 3 9; stroke-width: 3;}
|
||||
.diagram-container > svg path[data-edge-id^="contribute-"] {stroke-dasharray: 12 8; stroke-width: 3;}
|
||||
@@ -2,35 +2,59 @@ import {test} from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import {readFileSync} from 'node:fs';
|
||||
const read = p => readFileSync(p, 'utf8');
|
||||
const stages = ['Scope', 'Architecture', 'UI/UX', 'Planning', 'Delivery', 'UAT', 'Releases'];
|
||||
const stages = ['Scope', 'Architecture', 'UI/UX', 'Planning', 'Delivery', 'UAT', 'Security', 'Releases'];
|
||||
|
||||
test('Strategy opens at the autonomous lifecycle, with a matching direct landing story', () => {
|
||||
assert.match(read('sidebars/strategy.ts'), /strategySidebar: \['target-operating-model', 'index'/);
|
||||
assert.match(read('docs/strategy/target-operating-model.mdx'), /^sidebar_label: Target$/m);
|
||||
for (const id of ['index', '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 → Releases → Scope'));
|
||||
assert(primary.includes('Scope → Architecture → UI/UX → Planning → Delivery → UAT → Security → Releases → Scope'));
|
||||
assert(primary.includes('human-controlled code agent'));
|
||||
assert(primary.includes('TO-BE'));
|
||||
assert(!/only routes|People authorize|independent gates protect release/.test(primary));
|
||||
}
|
||||
});
|
||||
|
||||
test('both target diagrams have exactly seven sequential stages and a Releases to Scope return', () => {
|
||||
test('both target diagrams have eight stages, eight governed phases and eight human-controlled contributors', () => {
|
||||
for (const suffix of ['', '-mobile']) {
|
||||
const d = JSON.parse(read(`diagrams/strategy-target${suffix}.architecture.json`));
|
||||
const ids = stages.map(label => d.components.find(n => n.label === label)?.id);
|
||||
assert(ids.every(Boolean));
|
||||
assert.equal(new Set(ids).size, 7);
|
||||
assert.equal(new Set(ids).size, 8);
|
||||
const flow = d.connections.filter(e => ids.includes(e.from) && ids.includes(e.to));
|
||||
assert.deepEqual(flow.map(e => [e.from, e.to]), ids.map((id, i) => [id, ids[(i + 1) % ids.length]]));
|
||||
assert.match(flow.at(-1).label, /Repeat/);
|
||||
assert(flow.at(-1).via.length >= 2, 'explicit outside return corridor');
|
||||
|
||||
assert(d.components.some(n => n.label === 'Autonomous agent' && /Executes.*governs/.test(n.sublabel)));
|
||||
assert(d.components.some(n => n.label === 'Humans' && n.sublabel === 'Direct, guide and review'));
|
||||
assert(d.connections.some(e => e.from === 'humans' && e.to === 'agent'));
|
||||
assert(d.connections.some(e => e.from === 'agent' && e.to === ids[0]));
|
||||
assert.deepEqual(d.connections.filter(e => e.from === 'agent').map(e => e.to), ids);
|
||||
const contributors = d.components.filter(n => n.id.startsWith('code-'));
|
||||
assert.equal(contributors.length, 8);
|
||||
assert(contributors.every(n => n.label === 'Code agent' && n.sublabel === 'Human-controlled'));
|
||||
for (const id of ids) {
|
||||
assert(d.connections.some(e => e.from === `code-${id}` && e.to === id && e.variant === 'dashed'));
|
||||
assert(d.connections.some(e => e.from === 'agent' && e.to === id && e.variant === 'security'));
|
||||
}
|
||||
assert.equal(d.components.length, 18);
|
||||
if (!suffix) {
|
||||
const center = n => n.pos.map((v, i) => v + n.size[i] / 2);
|
||||
const agent = center(d.components.find(n => n.id === 'agent'));
|
||||
const phaseCenters = ids.map(id => center(d.components.find(n => n.id === id)));
|
||||
for (const axis of [0, 1]) {
|
||||
const values = phaseCenters.map(c => c[axis]);
|
||||
assert.equal(agent[axis], (Math.min(...values) + Math.max(...values)) / 2);
|
||||
}
|
||||
for (const code of contributors) {
|
||||
const phase = d.components.find(n => n.id === code.id.slice(5));
|
||||
const distance = n => Math.hypot(...center(n).map((v, i) => v - agent[i]));
|
||||
assert(distance(code) > distance(phase), `${code.id} is exterior`);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -28,10 +28,10 @@ test('mobile story diagrams preserve the same nodes and directed relationships',
|
||||
const name = /<ArchifyDiagram name="([^"]+)"[^>]+ story/.exec(page)[1];
|
||||
const desktop = JSON.parse(read(`diagrams/${name}.architecture.json`));
|
||||
const mobile = JSON.parse(read(`diagrams/${name}-mobile.architecture.json`));
|
||||
assert(desktop.components.length >= 5 && desktop.components.length <= (name === 'strategy-target' ? 9 : 7), name);
|
||||
assert(desktop.components.length >= 5 && desktop.components.length <= (name === 'strategy-target' ? 18 : 7), name);
|
||||
const semantic = d => ({nodes:d.components.map(({pos,size,...n})=>n),edges:d.connections.map(({fromSide,toSide,via,labelAt,...e})=>e)});
|
||||
assert.deepEqual(semantic(desktop), semantic(mobile), name);
|
||||
assert(desktop.connections.every(e => e.label && e.from !== e.to), name);
|
||||
assert(desktop.connections.every(e => (e.label || e.id.startsWith('govern-')) && e.from !== e.to), name);
|
||||
assert.equal(desktop.meta.legend.mode, 'hidden');
|
||||
}
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user