From 92a5b63954a0079f54e1387fa1869767f3a70d59 Mon Sep 17 00:00:00 2001 From: chahinebrini Date: Wed, 8 Jul 2026 05:37:32 +0200 Subject: [PATCH] feat(team): floating status chip + clean node header (Paperclip-style) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 'Active'/'Review' badge is now a floating pill straddling the card's top-right edge (light green / amber) instead of an inline tag — keeps the card uncluttered. - Move info + rename buttons out of the text row into an absolute top-right cluster so short names (Chahine, backyard, CI/CD) fit without truncating; smaller name font (12px); slightly wider node (182px) + matching grid. - Rename is discoverable: a pencil appears on node hover (still double-click too). Role labels for org-only / function nodes come from config.org 'role' (CEO, Product Owner, CI/CD, Strategist, Release) so they no longer show 'Implementer'. Co-Authored-By: Claude Opus 4.8 --- src/server/team.ts | 44 +++++++++++++++++++++++++++++--------------- 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/src/server/team.ts b/src/server/team.ts index 73757aa..b148492 100644 --- a/src/server/team.ts +++ b/src/server/team.ts @@ -111,12 +111,15 @@ export function renderTeamHtml(cwd: string): string {
${icon} - ${escapeHtml(n.label)} + ${escapeHtml(n.label)} ${sub ? `${sub}` : ''} - ${badge} +
+
+ ${title ? `` : ''}
+ ${badge} ${title ? `` : ''}
${status}
`; @@ -158,8 +161,8 @@ export function renderTeamHtml(cwd: string): string { .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; } + .children.wrap { display: grid; grid-template-columns: repeat(var(--cols, 3), 182px); justify-content: center; column-gap: 11px; row-gap: 46px; } + .children.wrap .subtree { width: 182px; } .team-toolbar { display: flex; align-items: center; gap: 8px; padding: 0 6px 8px; } .tb-spacer { flex: 1; } @@ -173,8 +176,7 @@ export function renderTeamHtml(cwd: string): string { .node { position: relative; - width: 168px; - overflow: hidden; + width: 182px; background: linear-gradient(180deg, rgba(255,255,255,.02), rgba(0,0,0,.10)), var(--surface); border: 1px solid var(--border); border-radius: 14px; @@ -184,19 +186,24 @@ export function renderTeamHtml(cwd: string): string { } @keyframes nodeEnter { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } } .node:hover { border-color: rgba(88,166,255,.5); } - .node-top { display: flex; align-items: center; gap: 10px; } + .node-top { display: flex; align-items: center; gap: 10px; padding-right: 34px; } + .node-actions { position: absolute; top: 9px; right: 10px; display: flex; align-items: center; gap: 5px; z-index: 4; } .node-icon { width: 32px; height: 32px; flex: 0 0 auto; display: inline-grid; place-items: center; border-radius: 9px; background: var(--raised); border: 1px solid var(--border); } .node-icon .mono { font: 800 11px/1 var(--font-mono); color: var(--pv, var(--muted)); } .node-id { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1; } - .node-name { font-weight: 700; font-size: 13px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; outline: none; border-radius: 4px; } + .node-name { font-weight: 700; font-size: 12px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; outline: none; border-radius: 4px; } + .node-edit { flex: 0 0 auto; width: 18px; height: 18px; border-radius: 50%; border: 1px solid var(--border); background: var(--raised); color: var(--muted); font: 10px/1 var(--font-sans); cursor: pointer; align-self: flex-start; opacity: 0; transition: opacity 140ms, color 140ms, border-color 140ms; } + .node:hover .node-edit { opacity: 1; } + .node-edit:hover { color: var(--text); border-color: var(--accent); } .node-name[contenteditable="true"] { background: rgba(88,166,255,.12); box-shadow: 0 0 0 1px rgba(88,166,255,.5); padding: 0 4px; cursor: text; } .node-role { display: block; max-width: 100%; font: 10.5px/1.2 var(--font-mono); color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; text-transform: uppercase; letter-spacing: .04em; } .node-model { text-transform: none; letter-spacing: 0; } - .node-badge { margin-left: auto; align-self: flex-start; font: 700 9px/1 var(--font-mono); text-transform: uppercase; letter-spacing: .06em; padding: 3px 6px; border-radius: 999px; } + /* Floating status chip that straddles the top-right edge (Paperclip style). */ + .node-badge { position: absolute; top: -9px; right: 12px; z-index: 3; font: 700 9.5px/1 var(--font-mono); text-transform: capitalize; letter-spacing: .02em; padding: 4px 9px; border-radius: 999px; } .node-badge:empty { display: none; } - .node-badge.active { color: #adf2c7; background: rgba(34,197,94,.16); border: 1px solid rgba(34,197,94,.4); } - .node-badge.review { color: #f2d59b; background: rgba(210,153,34,.16); border: 1px solid rgba(210,153,34,.4); } + .node-badge.active { color: #0b3d1e; background: #7ee2a8; box-shadow: 0 2px 12px rgba(34,197,94,.4); } + .node-badge.review { color: #3d2e07; background: #f0cf7a; box-shadow: 0 2px 12px rgba(210,153,34,.35); } .node-info-btn { flex: 0 0 auto; width: 20px; height: 20px; border-radius: 50%; border: 1px solid var(--border); background: var(--raised); color: var(--muted); font: 700 11px/1 var(--font-mono); cursor: pointer; align-self: flex-start; transition: color 150ms, border-color 150ms; } .node-info-btn:hover { color: var(--text); border-color: var(--accent); } @@ -375,12 +382,19 @@ export function renderTeamHtml(cwd: string): string { if (info) { info.hidden = !info.hidden; setTimeout(layoutEdges, 30); } }); - // Inline rename → PATCH /org/:id. - document.addEventListener('dblclick', function(e) { - var name = e.target.closest && e.target.closest('.node-name'); + // Inline rename → PATCH /org/:id (pencil click OR double-click the name). + function startEdit(name) { if (!name) return; name.setAttribute('contenteditable', 'true'); name.focus(); - document.getSelection().selectAllChildren(name); + try { document.getSelection().selectAllChildren(name); } catch (_) {} + } + document.addEventListener('click', function(e) { + var btn = e.target.closest && e.target.closest('.node-edit'); + if (btn) startEdit(btn.closest('.node').querySelector('.node-name')); + }); + document.addEventListener('dblclick', function(e) { + var name = e.target.closest && e.target.closest('.node-name'); + if (name) startEdit(name); }); function commitRename(name) { var card = name.closest('.node'); if (!card) return;