From 0d79e8fbd6c591d7ab70e703d258319154b68ca5 Mon Sep 17 00:00:00 2001 From: chahinebrini Date: Wed, 8 Jul 2026 05:16:39 +0200 Subject: [PATCH] feat(team): wrap wide leaf rows into a grid + fit-to-screen button MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Compact the tree so upper levels stay close together: - a parent with >4 leaf children now lays them out in a square-ish grid (cols = ceil(sqrt(n))) instead of one wide row; connectors drop from a fixed bus Y so the elbows stay clean across grid rows; - a 'Fit' toolbar button scales the whole chart to the viewport width via CSS zoom (reflows, so scroll shrinks) and toggles back to 100%. Cuts the rebreak org width ~2400px→~1830px, and Fit shows the entire tree at once. Co-Authored-By: Claude Opus 4.8 --- src/server/team.ts | 41 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/src/server/team.ts b/src/server/team.ts index b49a8fb..902ae99 100644 --- a/src/server/team.ts +++ b/src/server/team.ts @@ -130,8 +130,13 @@ export function renderTeamHtml(cwd: string): string { const rb = b.agent ? agentCfg[b.agent]?.role : undefined; return (ROLE_ORDER[ra ?? ''] ?? 5) - (ROLE_ORDER[rb ?? ''] ?? 5) || a.label.localeCompare(b.label); }); + // Wrap a wide row of leaf children into a compact grid so a broad subtree + // doesn't spread the whole tree — keeps upper levels close together. + const allLeaves = kids.every((k) => !(childrenOf.get(k.id)?.length)); + const wrap = allLeaves && kids.length > 4; + const cols = wrap ? Math.min(4, Math.ceil(Math.sqrt(kids.length))) : 0; const childrenHtml = kids.length - ? `
${kids.map((k) => renderSubtree(k.id)).join('')}
` + ? `
${kids.map((k) => renderSubtree(k.id)).join('')}
` : ''; return `
${renderNode(n)}${childrenHtml}
`; } @@ -152,6 +157,13 @@ export function renderTeamHtml(cwd: string): string { .edges { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; z-index: 0; overflow: visible; } .subtree { position: relative; z-index: 1; display: inline-flex; flex-direction: column; align-items: center; } .children { display: flex; flex-direction: row; align-items: flex-start; justify-content: center; gap: 11px; margin-top: 42px; } + /* Wide leaf rows collapse into a compact grid (connectors drop from a fixed bus). */ + .children.wrap { display: grid; grid-template-columns: repeat(var(--cols, 3), 168px); justify-content: center; column-gap: 11px; row-gap: 46px; } + .children.wrap .subtree { width: 168px; } + + .team-toolbar { display: flex; justify-content: flex-end; gap: 8px; padding: 0 6px 8px; } + .fit-btn { min-height: 30px; padding: 0 13px; border-radius: 8px; border: 1px solid var(--border); background: var(--surface); color: var(--muted); font: 600 12px/1 var(--font-sans); cursor: pointer; transition: color 150ms, border-color 150ms; } + .fit-btn:hover { color: var(--text); border-color: var(--accent); } .node { position: relative; @@ -210,6 +222,9 @@ export function renderTeamHtml(cwd: string): string { ${pageHeader(config.projectName, 'team')} +
+ +