style(portal): tighten stable overview and workspace desk

This commit is contained in:
rcourtman
2026-03-27 23:44:31 +00:00
parent 319fe1d4fa
commit 42a3deac1d
6 changed files with 101 additions and 28 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
{
"source_hash": "5c0116abefe33e38423a53786fa29fe46555d46fa8bc8dcea9ca2063cb1a7cc2",
"source_hash": "af74be942dbe9fa37b8c3eb059ba5434ea54ab2369d0d3da5c570049f5caab6b",
"build_inputs": [
"package.json",
"tsconfig.json",
+38 -7
View File
@@ -720,6 +720,37 @@ header .logout-btn:hover,
.overview-side-card-primary {
background: var(--panel-strong);
}
.overview-side-card-stable {
display: flex;
flex-direction: column;
gap: 12px;
}
.overview-stable-list {
display: flex;
flex-direction: column;
gap: 10px;
}
.overview-stable-item {
display: flex;
flex-direction: column;
gap: 4px;
padding: 12px 0;
border-top: 1px solid var(--line);
}
.overview-stable-item:first-child {
padding-top: 2px;
border-top: none;
}
.overview-stable-item strong {
font-size: 13px;
line-height: 1.3;
color: var(--ink);
}
.overview-stable-item span {
font-size: 12px;
line-height: 1.55;
color: var(--ink-soft);
}
.overview-next-checklist {
display: flex;
flex-direction: column;
@@ -1053,8 +1084,8 @@ header .logout-btn:hover,
margin-top: 16px;
display: flex;
flex-direction: column;
gap: 12px;
padding: 16px;
gap: 10px;
padding: 14px;
border-radius: var(--radius-sm);
background: var(--panel-muted);
color: var(--ink-soft);
@@ -1067,13 +1098,13 @@ header .logout-btn:hover,
.workspace-management-empty-grid {
display: grid;
grid-template-columns: minmax(0, 1fr);
gap: 12px;
gap: 10px;
}
.workspace-management-empty-card {
display: flex;
flex-direction: column;
gap: 10px;
padding: 14px;
gap: 8px;
padding: 12px;
border: 1px solid var(--line);
border-radius: 12px;
background: #fff;
@@ -1106,13 +1137,13 @@ header .logout-btn:hover,
.workspace-management-empty-checklist {
display: flex;
flex-direction: column;
gap: 10px;
gap: 8px;
}
.workspace-management-empty-check {
display: flex;
flex-direction: column;
gap: 4px;
padding: 12px;
padding: 10px 11px;
border: 1px solid var(--line);
border-radius: 10px;
background: rgba(255, 255, 255, 0.78);
+2 -2
View File
@@ -1765,7 +1765,7 @@
function renderAttentionPanel(workspaces) {
var attention = attentionWorkspaces(workspaces);
if (!attention.length) {
return '<div class="overview-side-card"><div class="account-panel-kicker">Attention</div><h4>Fleet is stable</h4><p>Every visible hosted workspace currently reports a healthy posture.</p></div>';
return '<div class="overview-side-card overview-side-card-stable"><div class="account-panel-kicker">Attention</div><h4>Fleet is stable</h4><p>Every visible hosted workspace currently reports a healthy posture.</p><div class="overview-stable-list"><div class="overview-stable-item"><strong>Healthy now</strong><span>All visible hosted workspaces are clear for routine operator work.</span></div><div class="overview-stable-item"><strong>Use Team only for change</strong><span>Keep roster edits explicit instead of mixing them into normal workspace work.</span></div><div class="overview-stable-item"><strong>Keep billing separate</strong><span>Use account services only when the task is commercial, not operational.</span></div></div></div>';
}
var items = attention.slice(0, 3).map(function(workspace) {
return '<div class="overview-alert-row"><div class="overview-alert-main"><strong>' + escapeHTML(workspace.display_name) + "</strong><span>" + escapeHTML(workspaceStatusCopy(workspace)) + "</span></div>" + healthBadgeHTML(workspace) + "</div>";
@@ -1855,7 +1855,7 @@
workspaceDeskActions += '<button type="button" class="btn-secondary btn-compact" data-action="open-billing" data-account-id="' + escapeAttr(account.id) + '">Manage billing</button>';
}
workspaceDeskActions += '<button type="button" class="btn-secondary btn-compact" data-action="toggle-team" data-account-id="' + escapeAttr(account.id) + '" data-shell-target="team">Manage team</button>';
workspaceManagement = '<section class="workspace-management-panel" id="workspace-management-' + escapeAttr(account.id) + '"><div class="workspace-management-header"><div><div class="account-panel-kicker">Workspace management</div><h3>Review one workspace at a time</h3><p>Select a workspace from the fleet to review its lifecycle state and run explicit management actions.</p></div><button type="button" class="btn-secondary btn-compact" id="workspace-management-close-' + escapeAttr(account.id) + '" data-action="clear-workspace-selection" data-account-id="' + escapeAttr(account.id) + '">Done</button></div><div class="workspace-management-empty" id="workspace-management-empty-' + escapeAttr(account.id) + '"><div class="workspace-management-empty-copy">Choose a workspace from the fleet to open its management desk.</div><div class="workspace-management-empty-grid"><div class="workspace-management-empty-card"><div class="account-panel-kicker">Account desk</div><h4>Keep account-wide actions close</h4><p>Use the desk for one workspace at a time while keeping billing, team, and create-workspace actions within reach.</p><div class="workspace-management-empty-actions">' + workspaceDeskActions + '</div></div><div class="workspace-management-empty-card workspace-management-empty-card-muted"><div class="account-panel-kicker">Review flow</div><div class="workspace-management-empty-checklist"><div class="workspace-management-empty-check"><strong>1. Pick the workspace</strong><span>Select a fleet row to bring its health, lifecycle, and next action into this desk.</span></div><div class="workspace-management-empty-check"><strong>2. Check posture first</strong><span>Treat checking or needs-attention workspaces as review work, not routine operations.</span></div><div class="workspace-management-empty-check"><strong>3. Escalate account changes separately</strong><span>Billing and team changes stay account-wide even when you are focused on one workspace.</span></div></div></div></div></div><div class="workspace-management-content" id="workspace-management-content-' + escapeAttr(account.id) + '" hidden><div class="workspace-management-meta" id="workspace-management-meta-' + escapeAttr(account.id) + '"></div><h4 id="workspace-management-title-' + escapeAttr(account.id) + '"></h4><p class="workspace-management-summary" id="workspace-management-summary-' + escapeAttr(account.id) + '"></p><div class="workspace-management-facts"><div class="workspace-management-fact"><span>Health</span><strong id="workspace-management-health-' + escapeAttr(account.id) + '"></strong></div><div class="workspace-management-fact"><span>Lifecycle</span><strong id="workspace-management-lifecycle-' + escapeAttr(account.id) + '"></strong></div><div class="workspace-management-fact"><span>Created</span><strong id="workspace-management-created-' + escapeAttr(account.id) + '"></strong></div></div><div class="workspace-management-guidance" id="workspace-management-guidance-' + escapeAttr(account.id) + '"></div><div class="workspace-management-actions"><button type="button" class="btn-danger" id="workspace-management-action-' + escapeAttr(account.id) + '" data-action="workspace-action" data-account-id="' + escapeAttr(account.id) + '">Manage workspace</button></div></div></section>';
workspaceManagement = '<section class="workspace-management-panel" id="workspace-management-' + escapeAttr(account.id) + '"><div class="workspace-management-header"><div><div class="account-panel-kicker">Workspace management</div><h3>Review one workspace at a time</h3><p>Select a workspace from the fleet to review its lifecycle state and run explicit management actions.</p></div><button type="button" class="btn-secondary btn-compact" id="workspace-management-close-' + escapeAttr(account.id) + '" data-action="clear-workspace-selection" data-account-id="' + escapeAttr(account.id) + '">Done</button></div><div class="workspace-management-empty" id="workspace-management-empty-' + escapeAttr(account.id) + '"><div class="workspace-management-empty-copy">Pick a workspace to open its operator desk.</div><div class="workspace-management-empty-grid"><div class="workspace-management-empty-card"><div class="account-panel-kicker">Account desk</div><h4>Keep account actions close</h4><p>Workspace review happens here while billing, team, and create-workspace actions stay within reach.</p><div class="workspace-management-empty-actions">' + workspaceDeskActions + '</div></div><div class="workspace-management-empty-card workspace-management-empty-card-muted"><div class="account-panel-kicker">Desk flow</div><div class="workspace-management-empty-checklist"><div class="workspace-management-empty-check"><strong>Pick the workspace</strong><span>Select a fleet row to load its health, lifecycle, and next management action.</span></div><div class="workspace-management-empty-check"><strong>Read posture first</strong><span>Treat checking or needs-attention workspaces as review work, not routine operations.</span></div><div class="workspace-management-empty-check"><strong>Escalate account changes separately</strong><span>Billing and team changes stay account-wide even when you are focused on one workspace.</span></div></div></div></div></div><div class="workspace-management-content" id="workspace-management-content-' + escapeAttr(account.id) + '" hidden><div class="workspace-management-meta" id="workspace-management-meta-' + escapeAttr(account.id) + '"></div><h4 id="workspace-management-title-' + escapeAttr(account.id) + '"></h4><p class="workspace-management-summary" id="workspace-management-summary-' + escapeAttr(account.id) + '"></p><div class="workspace-management-facts"><div class="workspace-management-fact"><span>Health</span><strong id="workspace-management-health-' + escapeAttr(account.id) + '"></strong></div><div class="workspace-management-fact"><span>Lifecycle</span><strong id="workspace-management-lifecycle-' + escapeAttr(account.id) + '"></strong></div><div class="workspace-management-fact"><span>Created</span><strong id="workspace-management-created-' + escapeAttr(account.id) + '"></strong></div></div><div class="workspace-management-guidance" id="workspace-management-guidance-' + escapeAttr(account.id) + '"></div><div class="workspace-management-actions"><button type="button" class="btn-danger" id="workspace-management-action-' + escapeAttr(account.id) + '" data-action="workspace-action" data-account-id="' + escapeAttr(account.id) + '">Manage workspace</button></div></div></section>';
}
var workspaceHTML = workspaces.length ? '<div class="workspace-list-wrap"><div class="workspace-list-toolbar"><div class="workspace-list-summary">Review the hosted fleet, open a workspace, and keep lifecycle actions explicit.</div><div class="workspace-list-stats"><span class="workspace-list-stat"><strong>' + String(workspaces.length) + '</strong> total</span><span class="workspace-list-stat"><strong>' + String(healthyCount) + '</strong> healthy</span><span class="workspace-list-stat"><strong>' + String(checkingCount) + '</strong> checking</span><span class="workspace-list-stat workspace-list-stat-attention"><strong>' + String(unhealthyCount) + '</strong> needs attention</span></div></div><div class="workspace-list-head"><span>Workspace</span><span>Health</span><span>Lifecycle</span><span>Actions</span></div><div class="workspace-list">' + workspaces.map(function(workspace) {
return renderWorkspaceCard(account, workspace, accountAPIBasePath);
@@ -168,9 +168,9 @@ describe('shell view', function() {
expect(html).toContain('Open workspace');
expect(html).toContain('data-action="select-workspace"');
expect(html).toContain('Workspace management');
expect(html).toContain('Choose a workspace from the fleet to open its management desk.');
expect(html).toContain('Review flow');
expect(html).toContain('Pick the workspace');
expect(html).toContain('Pick a workspace to open its operator desk.');
expect(html).toContain('Desk flow');
expect(html).toContain('Read posture first');
expect(html).toContain('Escalate account changes separately');
expect(html).toContain('data-action="clear-workspace-selection"');
expect(html).toContain('Team management');
@@ -163,10 +163,15 @@ function renderAttentionPanel(workspaces: PortalWorkspaceSummary[]): string {
var attention = attentionWorkspaces(workspaces);
if (!attention.length) {
return (
'<div class="overview-side-card">' +
'<div class="overview-side-card overview-side-card-stable">' +
'<div class="account-panel-kicker">Attention</div>' +
'<h4>Fleet is stable</h4>' +
'<p>Every visible hosted workspace currently reports a healthy posture.</p>' +
'<div class="overview-stable-list">' +
'<div class="overview-stable-item"><strong>Healthy now</strong><span>All visible hosted workspaces are clear for routine operator work.</span></div>' +
'<div class="overview-stable-item"><strong>Use Team only for change</strong><span>Keep roster edits explicit instead of mixing them into normal workspace work.</span></div>' +
'<div class="overview-stable-item"><strong>Keep billing separate</strong><span>Use account services only when the task is commercial, not operational.</span></div>' +
'</div>' +
'</div>'
);
}
@@ -513,20 +518,20 @@ function renderAccountWorkspaceSection(account: PortalAccountSummary, accountAPI
'<div class="workspace-management-empty" id="workspace-management-empty-' +
escapeAttr(account.id) +
'">' +
'<div class="workspace-management-empty-copy">Choose a workspace from the fleet to open its management desk.</div>' +
'<div class="workspace-management-empty-copy">Pick a workspace to open its operator desk.</div>' +
'<div class="workspace-management-empty-grid">' +
'<div class="workspace-management-empty-card">' +
'<div class="account-panel-kicker">Account desk</div>' +
'<h4>Keep account-wide actions close</h4>' +
'<p>Use the desk for one workspace at a time while keeping billing, team, and create-workspace actions within reach.</p>' +
'<h4>Keep account actions close</h4>' +
'<p>Workspace review happens here while billing, team, and create-workspace actions stay within reach.</p>' +
'<div class="workspace-management-empty-actions">' + workspaceDeskActions + '</div>' +
'</div>' +
'<div class="workspace-management-empty-card workspace-management-empty-card-muted">' +
'<div class="account-panel-kicker">Review flow</div>' +
'<div class="account-panel-kicker">Desk flow</div>' +
'<div class="workspace-management-empty-checklist">' +
'<div class="workspace-management-empty-check"><strong>1. Pick the workspace</strong><span>Select a fleet row to bring its health, lifecycle, and next action into this desk.</span></div>' +
'<div class="workspace-management-empty-check"><strong>2. Check posture first</strong><span>Treat checking or needs-attention workspaces as review work, not routine operations.</span></div>' +
'<div class="workspace-management-empty-check"><strong>3. Escalate account changes separately</strong><span>Billing and team changes stay account-wide even when you are focused on one workspace.</span></div>' +
'<div class="workspace-management-empty-check"><strong>Pick the workspace</strong><span>Select a fleet row to load its health, lifecycle, and next management action.</span></div>' +
'<div class="workspace-management-empty-check"><strong>Read posture first</strong><span>Treat checking or needs-attention workspaces as review work, not routine operations.</span></div>' +
'<div class="workspace-management-empty-check"><strong>Escalate account changes separately</strong><span>Billing and team changes stay account-wide even when you are focused on one workspace.</span></div>' +
'</div>' +
'</div>' +
'</div>' +
@@ -795,6 +795,43 @@ header .logout-btn:hover,
background: var(--panel-strong);
}
.overview-side-card-stable {
display: flex;
flex-direction: column;
gap: 12px;
}
.overview-stable-list {
display: flex;
flex-direction: column;
gap: 10px;
}
.overview-stable-item {
display: flex;
flex-direction: column;
gap: 4px;
padding: 12px 0;
border-top: 1px solid var(--line);
}
.overview-stable-item:first-child {
padding-top: 2px;
border-top: none;
}
.overview-stable-item strong {
font-size: 13px;
line-height: 1.3;
color: var(--ink);
}
.overview-stable-item span {
font-size: 12px;
line-height: 1.55;
color: var(--ink-soft);
}
.overview-next-checklist {
display: flex;
flex-direction: column;
@@ -1181,8 +1218,8 @@ header .logout-btn:hover,
margin-top: 16px;
display: flex;
flex-direction: column;
gap: 12px;
padding: 16px;
gap: 10px;
padding: 14px;
border-radius: var(--radius-sm);
background: var(--panel-muted);
color: var(--ink-soft);
@@ -1197,14 +1234,14 @@ header .logout-btn:hover,
.workspace-management-empty-grid {
display: grid;
grid-template-columns: minmax(0, 1fr);
gap: 12px;
gap: 10px;
}
.workspace-management-empty-card {
display: flex;
flex-direction: column;
gap: 10px;
padding: 14px;
gap: 8px;
padding: 12px;
border: 1px solid var(--line);
border-radius: 12px;
background: #fff;
@@ -1242,14 +1279,14 @@ header .logout-btn:hover,
.workspace-management-empty-checklist {
display: flex;
flex-direction: column;
gap: 10px;
gap: 8px;
}
.workspace-management-empty-check {
display: flex;
flex-direction: column;
gap: 4px;
padding: 12px;
padding: 10px 11px;
border: 1px solid var(--line);
border-radius: 10px;
background: rgba(255, 255, 255, 0.78);