Files
documentation/scripts/roadmap-viewer.mjs
jarvis-at-skic 1a2d16bc91
Build and publish Eldorado documentation / build (pull_request) Successful in 1m30s
docs: [verified] answer the six-month AI Architect assignment
2026-10-08 16:59:36 +00:00

66 lines
4.3 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Native Workflow v2 owns node geometry, mainPath and routing. The portal adds
// multiline typography: upstream's three single-line labels are too small for
// a Roadmap reading view. Do not change the pinned vendor or graph semantics.
const escape = text => text.replaceAll('&', '&amp;').replaceAll('<', '&lt;').replaceAll('>', '&gt;').replaceAll('"', '&quot;');
const wrap = (text, limit = 21) => {
const lines = [];
for (const word of text.split(/\s+/)) {
if (!lines.length || `${lines.at(-1)} ${word}`.length > limit) lines.push(word);
else lines[lines.length - 1] += ` ${word}`;
}
return lines;
};
export function roadmapViewer(html, spec) {
if (spec.diagram_type !== 'workflow' || spec.schema_version !== 2 || ![2, 6].includes(spec.nodes.length)) throw new Error('Roadmap workflow contract drift');
const boxes = [];
for (const [index, node] of spec.nodes.entries()) {
const start = html.indexOf(`<g id="node-${node.id}"`);
const next = html.indexOf('<text data-node-label=', start);
const end = html.indexOf('</g>', next) + 4;
if (start < 0 || next < start || end < next) throw new Error(`Roadmap node drift: ${node.id}`);
const original = html.slice(start, end);
const rect = /<rect x="([\d.-]+)" y="([\d.-]+)" width="([\d.]+)" height="([\d.]+)"/.exec(original);
if (!rect || (original.match(/<text /g) || []).length !== 2) throw new Error(`Roadmap text contract drift: ${node.id}`);
const [x,y,w,h] = rect.slice(1).map(Number);
boxes.push({x,y,w,h});
const text = (value, top, size, cls, attrs = 'data-detail="context"') => {
const lines = wrap(value, size === 30 ? 18 : 21);
const maxLines = top === 160 ? 3 : 2;
if (lines.length > maxLines || lines.some(line => line.length > 21)) throw new Error(`Roadmap copy exceeds card capacity: ${node.id}`);
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' ? ['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');
// Preserve native node title/metadata and rectangles; replace sigil/text only.
// Sigils are nested SVG groups. Anchor on the two native node rectangles.
const rectangles = [...original.matchAll(/<rect\b[^>]*\/>/g)];
if (rectangles.length !== 2) throw new Error('Roadmap shape contract drift');
const prefixEnd = rectangles[1].index + rectangles[1][0].length;
const replacement = original.slice(0,prefixEnd).replaceAll('rx="6"', 'rx="0"') + content + '</g>';
html = html.slice(0,start) + replacement + html.slice(end);
}
// The native mobile return channel sits outside the card column.
const left = Math.min(...boxes.map(b => b.x)) - (spec.edges.some(e => e.route === 'return-left') ? 80 : 12);
const top = Math.min(...boxes.map(b => b.y)) - 12;
const width = Math.max(...boxes.map(b => b.x+b.w)) - left + 12;
const height = Math.max(...boxes.map(b => b.y+b.h)) - top + 12;
// One editorial lane; its title/frame would repeat the page's framing.
html = html.replace(/<rect\b[^>]*data-composition-frame-kind="lane"[^>]*\/>\s*<text\b[^>]*>[\s\S]*?<\/text>/g, '');
const roots = [...html.matchAll(/<svg\b[^>]*role="img"[^>]*>/g)];
if (roots.length !== 1) throw new Error('Roadmap SVG contract drift');
const root = roots[0][0].replace(/viewBox="[^"]*"/, `viewBox="${left} ${top} ${width} ${height}"`);
const styles = `<style id="eldorado-roadmap-typography">
html[data-embed="true"] .diagram-container > svg {max-height:calc(100vh - 84px);}
[data-node-id] text {font-family: Arial, sans-serif;}
svg[data-preset="classic"] [data-node-id] > rect.c-backend,
svg[data-preset="editorial"] [data-node-id] > rect.c-backend {fill:var(--panel); stroke:var(--border);}
.c-grid {opacity:0;}
</style>`;
html = html.replace(roots[0][0], root + styles);
return html;
}