// Native editable geometry and edges; scoped editorial typography for Stage 01. export const isSkillsStory = name => /^strategy-(governance|quality|adoption|measurement|baseline|maintenance)(-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 detailSize = spec.meta.output.includes('-mobile/') ? 34 : 32; for (const node of spec.components) { const pattern = new RegExp(`]*data-node-id="${node.id}"[^>]*>[\\s\\S]*?`); const match = html.match(pattern); if (!match) throw new Error(`Missing Skills node: ${node.id}`); const original = match[0]; const rectangles = [...original.matchAll(/]*class="c-[^"]+"[^>]*\/>/g)]; if (rectangles.length !== 2) throw new Error('Skills shape contract drift'); const [x,y] = node.pos; const lines = node.sublabel.split('\n'); if (!node.tag || !node.sublabel || lines.length > 4) throw new Error(`Skills copy exceeds capacity: ${node.id}`); const text = (value, top, size, attrs) => `${escape(value)}`; const content = text(node.tag, 40, detailSize, 'class="t-muted" data-detail="context"') + text(node.label, 89, 36, 'class="t-primary" data-node-label="" font-weight="700"') + lines.map((line,i) => text(line, 138+i*42, detailSize, 'class="t-primary" data-detail="context"')).join(''); const prefix = original.slice(0,rectangles[1].index+rectangles[1][0].length).replaceAll('rx="6"','rx="0"'); html = html.replace(original,prefix+content+''); } const roots = [...html.matchAll(/]*role="img"[^>]*>/g)]; if (roots.length !== 1) throw new Error('Skills SVG contract drift'); const [width,height] = spec.meta.viewBox; const root = roots[0][0].replace(/viewBox="[^"]*"/,`viewBox="0 0 ${width} ${height}"`); // Embedded in SVG so downloaded files retain typography without the viewer. html = html.replace(roots[0][0],root+``); // Native labels use tiny technical annotations. Enlarge both text and mask, // retaining the native edge identity, routing and explicit label position. html = html.replace(/(]*data-edge-id[^>]*>)([\s\S]*?)(<\/g>)/g, (all,open,body,close) => { const text = body.match(/]*)>([\s\S]*?)<\/text>/); if (!text) throw new Error('Skills edge label contract drift'); const x=Number(text[1]), y=Number(text[2]), width=text[4].replace(/&[^;]+;/g,'x').length*17+24; return open+``+text[0].replace('font-size="8"',`font-size="${detailSize}"`)+close; }); return html; }