feat(team): floating status chip + clean node header (Paperclip-style)
- '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 <noreply@anthropic.com>
This commit is contained in:
parent
2140a293ed
commit
92a5b63954
@ -111,12 +111,15 @@ export function renderTeamHtml(cwd: string): string {
|
|||||||
<div class="node-top">
|
<div class="node-top">
|
||||||
<span class="node-icon" style="--pv:${modelColor}">${icon}</span>
|
<span class="node-icon" style="--pv:${modelColor}">${icon}</span>
|
||||||
<span class="node-id">
|
<span class="node-id">
|
||||||
<span class="node-name" title="double-click to rename">${escapeHtml(n.label)}</span>
|
<span class="node-name" title="Click the pencil (or double-click) to rename">${escapeHtml(n.label)}</span>
|
||||||
${sub ? `<span class="node-role">${sub}</span>` : ''}
|
${sub ? `<span class="node-role">${sub}</span>` : ''}
|
||||||
</span>
|
</span>
|
||||||
${badge}
|
</div>
|
||||||
|
<div class="node-actions">
|
||||||
|
<button class="node-edit" type="button" title="Rename" aria-label="Rename">✎</button>
|
||||||
${title ? `<button class="node-info-btn" type="button" aria-label="Info">i</button>` : ''}
|
${title ? `<button class="node-info-btn" type="button" aria-label="Info">i</button>` : ''}
|
||||||
</div>
|
</div>
|
||||||
|
${badge}
|
||||||
${title ? `<div class="node-info" hidden>${escapeHtml(title)}</div>` : ''}
|
${title ? `<div class="node-info" hidden>${escapeHtml(title)}</div>` : ''}
|
||||||
<div class="agent-status">${status}</div>
|
<div class="agent-status">${status}</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
@ -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; }
|
.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; }
|
.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). */
|
/* 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 { display: grid; grid-template-columns: repeat(var(--cols, 3), 182px); justify-content: center; column-gap: 11px; row-gap: 46px; }
|
||||||
.children.wrap .subtree { width: 168px; }
|
.children.wrap .subtree { width: 182px; }
|
||||||
|
|
||||||
.team-toolbar { display: flex; align-items: center; gap: 8px; padding: 0 6px 8px; }
|
.team-toolbar { display: flex; align-items: center; gap: 8px; padding: 0 6px 8px; }
|
||||||
.tb-spacer { flex: 1; }
|
.tb-spacer { flex: 1; }
|
||||||
@ -173,8 +176,7 @@ export function renderTeamHtml(cwd: string): string {
|
|||||||
|
|
||||||
.node {
|
.node {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 168px;
|
width: 182px;
|
||||||
overflow: hidden;
|
|
||||||
background: linear-gradient(180deg, rgba(255,255,255,.02), rgba(0,0,0,.10)), var(--surface);
|
background: linear-gradient(180deg, rgba(255,255,255,.02), rgba(0,0,0,.10)), var(--surface);
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
border-radius: 14px;
|
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; } }
|
@keyframes nodeEnter { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }
|
||||||
.node:hover { border-color: rgba(88,166,255,.5); }
|
.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 { 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-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-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-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-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-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:empty { display: none; }
|
||||||
.node-badge.active { color: #adf2c7; background: rgba(34,197,94,.16); border: 1px solid rgba(34,197,94,.4); }
|
.node-badge.active { color: #0b3d1e; background: #7ee2a8; box-shadow: 0 2px 12px 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.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 { 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); }
|
.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); }
|
if (info) { info.hidden = !info.hidden; setTimeout(layoutEdges, 30); }
|
||||||
});
|
});
|
||||||
|
|
||||||
// Inline rename → PATCH /org/:id.
|
// Inline rename → PATCH /org/:id (pencil click OR double-click the name).
|
||||||
document.addEventListener('dblclick', function(e) {
|
function startEdit(name) {
|
||||||
var name = e.target.closest && e.target.closest('.node-name');
|
|
||||||
if (!name) return;
|
if (!name) return;
|
||||||
name.setAttribute('contenteditable', 'true'); name.focus();
|
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) {
|
function commitRename(name) {
|
||||||
var card = name.closest('.node'); if (!card) return;
|
var card = name.closest('.node'); if (!card) return;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user