@@ -417,6 +426,62 @@ export function renderTeamHtml(cwd: string): string {
var fb = document.getElementById('fitBtn');
if (fb) fb.addEventListener('click', toggleFit);
+ // ── Flow demo: choreographed pulses that walk the delegation lifecycle ──
+ function sleep(ms) { return new Promise(function(r){ setTimeout(r, ms); }); }
+ function ancestors(id) { var a = [id], c = parentOf[id]; while (c) { a.push(c); c = parentOf[c]; } return a; }
+ // Pulse from one node to another along the tree (up to the common ancestor,
+ // then down). Each edge is coloured by its real direction. Returns duration.
+ function pulseBetween(fromId, toId) {
+ if (!edgeMap || (!edgeMap[fromId] && !edgeMap[toId] && fromId !== toId)) { /* still fire best-effort */ }
+ var A = ancestors(fromId), B = ancestors(toId);
+ var Bidx = {}; B.forEach(function(id, i) { Bidx[id] = i; });
+ var lca = null, ai = 0;
+ for (var i = 0; i < A.length; i++) { if (Bidx[A[i]] != null) { lca = A[i]; ai = i; break; } }
+ if (lca == null) return EDGE_DUR;
+ var seq = [];
+ for (var u = 0; u < ai; u++) seq.push({ nodeId: A[u], dir: 'up' }); // from → LCA
+ for (var d = Bidx[lca] - 1; d >= 0; d--) seq.push({ nodeId: B[d], dir: 'down' }); // LCA → to
+ seq.forEach(function(step, k) { setTimeout(function(){ firePulseEdge(step.nodeId, step.dir); }, k * SEG); });
+ return Math.max(EDGE_DUR, seq.length * SEG + EDGE_DUR);
+ }
+
+ var DEMO = [
+ { from: 'claude', to: 'po-native', cap: 'claude definiert den Task und delegiert an PO Native' },
+ { from: 'po-native', to: 'backyard', cap: 'PO Native delegiert an den richtigen Agenten (backyard)' },
+ { from: 'backyard', to: 'po-native', cap: 'backyard liefert die Arbeit zurück an PO Native', rev: true },
+ { from: 'po-native', to: 'claude', cap: 'PO Native validiert → zurück zum Architekt-Review', rev: true },
+ { from: 'claude', to: 'cicd', cap: 'Architekt: Review OK → an CI/CD (sonst: reopen an den Agenten)' },
+ { from: 'cicd', to: 'ahmed', cap: 'CI/CD gibt an ahmed zum Testen' },
+ { from: 'ahmed', to: 'cicd', cap: 'ahmed meldet das Testergebnis zurück an CI/CD', rev: true },
+ { from: 'cicd', to: 'claude', cap: 'CI/CD zurück an Architekt → Approve & Push', rev: true },
+ { from: 'claude', to: 'strategist', cap: 'Manche Tasks: Architekt zieht den Strategist hinzu' }
+ ];
+ function showCaption(text, review) {
+ var el = document.getElementById('flowCaption');
+ if (!el) return;
+ if (!text) { el.hidden = true; el.textContent = ''; return; }
+ el.hidden = false; el.className = 'flow-caption' + (review ? ' review' : ''); el.textContent = text;
+ }
+ var demoRunning = false;
+ async function runDemo() {
+ if (demoRunning) return;
+ demoRunning = true;
+ var btn = document.getElementById('flowBtn');
+ if (btn) { btn.setAttribute('disabled', 'true'); }
+ layoutEdges(); // make sure edge geometry is current
+ for (var i = 0; i < DEMO.length; i++) {
+ var s = DEMO[i];
+ showCaption((i + 1) + '/' + DEMO.length + ' · ' + s.cap, s.rev);
+ var dur = pulseBetween(s.from, s.to);
+ await sleep(dur + 550);
+ }
+ showCaption('');
+ if (btn) btn.removeAttribute('disabled');
+ demoRunning = false;
+ }
+ var flowBtn = document.getElementById('flowBtn');
+ if (flowBtn) flowBtn.addEventListener('click', runDemo);
+
var rt; function relayout() { clearTimeout(rt); rt = setTimeout(function(){ if (fitted) { fitted = false; toggleFit(); } else layoutEdges(); }, 120); }
window.addEventListener('resize', relayout);
layoutEdges(); centerScroll();