fix(team): clean single traveling pulse + delegations start at the architect

- The pulse is now one dash with an effectively infinite gap, animated
  origin→target→off with fill:forwards — no wrap-around second bar and no
  snap-back to origin when it arrives (fixes the ghost bar). Slower, ease-in-out
  so it eases out of the origin card and into the target's last branch.
- Real delegation/review pulses now originate at the ARCHITECT (the root's
  single child), not the CEO above it — so a task assigned to an agent no longer
  visibly starts from the CEO. Cuts a 3-edge cascade to 2 for a PO-level agent.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
chahinebrini 2026-07-08 06:24:21 +02:00
parent c2939966b5
commit 1bb18d96b5

View File

@ -278,7 +278,7 @@ export function renderTeamHtml(cwd: string): string {
<script> <script>
(function() { (function() {
var NS = 'http://www.w3.org/2000/svg'; var NS = 'http://www.w3.org/2000/svg';
var SEG = 680, EDGE_DUR = 1500; var SEG = 720, EDGE_DUR = 1850;
var connDot = document.getElementById('conn-dot'); var connDot = document.getElementById('conn-dot');
var edgeMap = {}; // nodeId -> { fwd, rev } path from its parent var edgeMap = {}; // nodeId -> { fwd, rev } path from its parent
var parentOf = {}; // nodeId -> parent nodeId var parentOf = {}; // nodeId -> parent nodeId
@ -334,28 +334,41 @@ export function renderTeamHtml(cwd: string): string {
p.setAttribute('d', dir === 'up' ? e.rev : e.fwd); p.setAttribute('d', dir === 'up' ? e.rev : e.fwd);
svg.appendChild(p); svg.appendChild(p);
var len = p.getTotalLength(); var len = p.getTotalLength();
p.style.strokeDasharray = '20 ' + (len + 20); // One dash with an effectively infinite gap → no second dash can wrap
p.style.strokeDashoffset = String(len + 20); // around onto the path. It travels origin(0) → target(len) → off, and
// fill:forwards holds the off-path end so it never snaps back to origin.
p.style.strokeDasharray = '20 100000';
p.style.strokeDashoffset = '0';
var done = function() { var done = function() {
try { p.remove(); } catch (_) {} try { p.remove(); } catch (_) {}
// Only the final destination lights up — waypoints (e.g. CEO on a // Only the final destination lights up — waypoints (e.g. CEO on a
// peer-to-peer handoff) are just passed through, not "involved". // peer-to-peer handoff) are just passed through, not "involved".
if (doFlash) flash(dir === 'up' ? nodeById(parentOf[nodeId]) : nodeById(nodeId)); if (doFlash) flash(dir === 'up' ? nodeById(parentOf[nodeId]) : nodeById(nodeId));
}; };
if (p.animate) { p.animate([{ strokeDashoffset: len + 20 }, { strokeDashoffset: 0 }], { duration: EDGE_DUR, easing: 'cubic-bezier(.4,0,.5,1)' }).onfinish = done; } if (p.animate) {
else setTimeout(done, EDGE_DUR); // ease-in-out: slower leaving the origin card and arriving at the target's last branch.
p.animate([{ strokeDashoffset: 0 }, { strokeDashoffset: -(len + 24) }],
{ duration: EDGE_DUR, easing: 'cubic-bezier(.5,0,.5,1)', fill: 'forwards' }).onfinish = done;
} else setTimeout(done, EDGE_DUR);
} }
// A pulse that travels the whole path root→node (down) or node→root (up). // The delegation hub = the architect (the root's single child), so real
function pulsePath(nodeId, dir) { // pulses originate at the architect, not the CEO above it.
var chain = []; var cur = nodeId; function hubId() {
while (cur && edgeMap[cur]) { chain.push(cur); cur = parentOf[cur]; } // node → topChild var root = document.querySelector('.node[data-parent=""]') || document.querySelector('.node[data-node-id]');
if (dir === 'down') chain.reverse(); // topChild → node if (!root) return null;
chain.forEach(function(id, i) { setTimeout(function() { firePulseEdge(id, dir, i === chain.length - 1); }, i * SEG); }); var rid = root.getAttribute('data-node-id');
var kids = document.querySelectorAll('.node[data-parent="' + rid + '"]');
return kids.length === 1 ? kids[0].getAttribute('data-node-id') : rid;
} }
function pulseToAgent(agent, dir) { function pulseToAgent(agent, dir) {
var hub = hubId();
if (!hub) return;
document.querySelectorAll('.node[data-agent="' + (agent||'').replace(/"/g,'') + '"]').forEach(function(n) { document.querySelectorAll('.node[data-agent="' + (agent||'').replace(/"/g,'') + '"]').forEach(function(n) {
pulsePath(n.getAttribute('data-node-id'), dir); var id = n.getAttribute('data-node-id');
if (id === hub) return;
if (dir === 'down') pulseBetween(hub, id); // architect → agent (delegation)
else pulseBetween(id, hub); // agent → architect (review)
}); });
} }