docs: lead Strategy with autonomous SDLC cycle
Build and publish Eldorado documentation / build (pull_request) Successful in 51s

This commit is contained in:
jarvis-at-skic committed 2026-10-05 16:32:51 +00:00
1 parent e95b85afee
commit 29beebb379
14 files changed
+184 -333

No files matched your search

+2
View File
@@ -121,6 +121,8 @@ const fs = require('node:fs');
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();
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'}`);
await next.scrollIntoViewIfNeeded();
await (await (await next.elementHandle()).contentFrame()).waitForFunction(() => document.documentElement.dataset.theme === 'dark');
+53
View File
@@ -0,0 +1,53 @@
const {chromium} = require('playwright');
const assert = require('node:assert/strict');
const fs = require('node:fs');
(async () => {
const base = process.argv[2] || 'http://127.0.0.1:4173';
const output = `.cache/cycle-${process.argv[3] || 'local'}`;
fs.mkdirSync(output, {recursive:true});
const browser = await chromium.launch({headless:true, executablePath:process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE, args:['--no-sandbox']});
const results = [];
try {
for (const width of [1440, 390, 320]) {
const context = await browser.newContext({viewport:{width,height:1000}, colorScheme:'light'});
const page = await context.newPage();
await page.goto(`${base}/eldorado-v1/documentation/`, {waitUntil:'networkidle'});
if (width < 700) await page.getByRole('button', {name:'Toggle navigation bar'}).click();
const nav = page.getByRole('link', {name:'Strategy', exact:true}).filter({visible:true});
assert((await nav.getAttribute('href')).endsWith('/strategy/target-operating-model/'));
await nav.click();
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');
const iframe = page.locator(`.archify-panel--story > .story-${width > 700 ? 'desktop' : 'mobile'}`);
await iframe.scrollIntoViewIfNeeded();
const frame = await (await iframe.elementHandle()).contentFrame();
await frame.locator('svg[role="img"]').waitFor();
const metrics = await frame.evaluate(() => {
const svg = document.querySelector('svg[role="img"]');
const labels = [...svg.querySelectorAll('text[data-node-label]')].map(n => ({text:n.textContent,px:parseFloat(getComputedStyle(n).fontSize)*n.getScreenCTM().a}));
const edges = [...svg.querySelectorAll('path[data-edge-id^="stage-"]')].map(n => [n.dataset.edgeFrom,n.dataset.edgeTo,n.getAttribute('marker-end')]);
const overflow = [...svg.querySelectorAll('[data-node-id]')].flatMap(n => {
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 ids = ['scope','architecture','ui-ux','planning','delivery','uat','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(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.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));
}
await context.close();
}
assert.equal(results.length,6);
console.log('PASS 6 cycle routes/viewports: navigation, sidebar order, exact directed loop, arrowheads, >=14px labels, text bounds and no overflow');
} finally {await browser.close();}
})().catch(error=>{console.error(error);process.exitCode=1;});
+1 -1
View File
@@ -19,7 +19,7 @@ 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<=7,id);
assert(geometry.nodes.length>=5 && geometry.nodes.length<=(['index','target-operating-model'].includes(id)?9: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);
+3 -1
View File
@@ -4,6 +4,7 @@ import {execFileSync} from 'node:child_process';
const cli = 'vendor/archify/bin/archify.mjs';
const types = new Set(['architecture','workflow','sequence','dataflow','lifecycle']);
const inlineStyles = readFileSync(new URL('./inline-viewer.css', import.meta.url), 'utf8');
const cycleStyles = readFileSync(new URL('./cycle-viewer.css', import.meta.url), 'utf8');
const themeBridge = readFileSync(new URL('./viewer-theme-bridge.js', import.meta.url), 'utf8');
// This directory is generated-only and ignored by Git. Remove stale artifacts.
rmSync('static/diagrams', {recursive: true, force: true});
@@ -36,7 +37,8 @@ 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}`);
writeFileSync(output, viewer.replace('</head>', `<style id="eldorado-inline-controls">${inlineStyles}</style></head>`)
const storyStyles = ['strategy-target', 'strategy-target-mobile'].includes(name) ? cycleStyles : '';
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`);
}
+14
View File
@@ -0,0 +1,14 @@
/* Readable lifecycle story at inline scale, isolated from other diagrams. */
.diagram-container > svg text {
font-family: Arial, sans-serif;
letter-spacing: 0;
font-size: 18px;
}
.diagram-container > svg text[data-node-label] {
font-size: 24px;
transform: translateY(-3px);
}
.diagram-container > svg text[data-detail="context"] {
font-size: 18px;
transform: translateY(7px);
}
+36
View File
@@ -0,0 +1,36 @@
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'];
test('Strategy opens at the autonomous lifecycle, with a matching direct landing story', () => {
assert.match(read('sidebars/strategy.ts'), /strategySidebar: \['target-operating-model', 'index'/);
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('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', () => {
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);
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]));
}
});
+2 -2
View File
@@ -28,8 +28,8 @@ 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 <= 7, name);
const semantic = d => ({nodes:d.components.map(({pos,size,...n})=>n),edges:d.connections.map(({fromSide,toSide,...e})=>e)});
assert(desktop.components.length >= 5 && desktop.components.length <= (name === 'strategy-target' ? 9 : 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.equal(desktop.meta.legend.mode, 'hidden');