diff --git a/internal/cloudcp/portal/dist/build_manifest.json b/internal/cloudcp/portal/dist/build_manifest.json
index 2967777d98..401499c368 100644
--- a/internal/cloudcp/portal/dist/build_manifest.json
+++ b/internal/cloudcp/portal/dist/build_manifest.json
@@ -1,5 +1,5 @@
{
- "source_hash": "5c0116abefe33e38423a53786fa29fe46555d46fa8bc8dcea9ca2063cb1a7cc2",
+ "source_hash": "af74be942dbe9fa37b8c3eb059ba5434ea54ab2369d0d3da5c570049f5caab6b",
"build_inputs": [
"package.json",
"tsconfig.json",
diff --git a/internal/cloudcp/portal/dist/portal_app.css b/internal/cloudcp/portal/dist/portal_app.css
index b21f0980e6..4304600efa 100644
--- a/internal/cloudcp/portal/dist/portal_app.css
+++ b/internal/cloudcp/portal/dist/portal_app.css
@@ -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);
diff --git a/internal/cloudcp/portal/dist/portal_app.js b/internal/cloudcp/portal/dist/portal_app.js
index 317b0c022d..31b6ed9cb4 100644
--- a/internal/cloudcp/portal/dist/portal_app.js
+++ b/internal/cloudcp/portal/dist/portal_app.js
@@ -1765,7 +1765,7 @@
function renderAttentionPanel(workspaces) {
var attention = attentionWorkspaces(workspaces);
if (!attention.length) {
- return '
Attention
Fleet is stable
Every visible hosted workspace currently reports a healthy posture.
';
+ return 'Attention
Fleet is stable
Every visible hosted workspace currently reports a healthy posture.
Healthy nowAll visible hosted workspaces are clear for routine operator work.
Use Team only for changeKeep roster edits explicit instead of mixing them into normal workspace work.
Keep billing separateUse account services only when the task is commercial, not operational.
';
}
var items = attention.slice(0, 3).map(function(workspace) {
return '' + escapeHTML(workspace.display_name) + "" + escapeHTML(workspaceStatusCopy(workspace)) + "
" + healthBadgeHTML(workspace) + "
";
@@ -1855,7 +1855,7 @@
workspaceDeskActions += '';
}
workspaceDeskActions += '';
- workspaceManagement = 'Choose a workspace from the fleet to open its management desk.
Account desk
Keep account-wide actions close
Use the desk for one workspace at a time while keeping billing, team, and create-workspace actions within reach.
' + workspaceDeskActions + '
Review flow
1. Pick the workspaceSelect a fleet row to bring its health, lifecycle, and next action into this desk.
2. Check posture firstTreat checking or needs-attention workspaces as review work, not routine operations.
3. Escalate account changes separatelyBilling and team changes stay account-wide even when you are focused on one workspace.
';
+ workspaceManagement = 'Pick a workspace to open its operator desk.
Account desk
Keep account actions close
Workspace review happens here while billing, team, and create-workspace actions stay within reach.
' + workspaceDeskActions + '
Desk flow
Pick the workspaceSelect a fleet row to load its health, lifecycle, and next management action.
Read posture firstTreat checking or needs-attention workspaces as review work, not routine operations.
Escalate account changes separatelyBilling and team changes stay account-wide even when you are focused on one workspace.
';
}
var workspaceHTML = workspaces.length ? 'WorkspaceHealthLifecycleActions
' + workspaces.map(function(workspace) {
return renderWorkspaceCard(account, workspace, accountAPIBasePath);
diff --git a/internal/cloudcp/portal/frontend/src/shell_view.test.ts b/internal/cloudcp/portal/frontend/src/shell_view.test.ts
index 100c9518b6..a43da0e3dd 100644
--- a/internal/cloudcp/portal/frontend/src/shell_view.test.ts
+++ b/internal/cloudcp/portal/frontend/src/shell_view.test.ts
@@ -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');
diff --git a/internal/cloudcp/portal/frontend/src/shell_view.ts b/internal/cloudcp/portal/frontend/src/shell_view.ts
index 97ccab3baf..234c7f5f07 100644
--- a/internal/cloudcp/portal/frontend/src/shell_view.ts
+++ b/internal/cloudcp/portal/frontend/src/shell_view.ts
@@ -163,10 +163,15 @@ function renderAttentionPanel(workspaces: PortalWorkspaceSummary[]): string {
var attention = attentionWorkspaces(workspaces);
if (!attention.length) {
return (
- '
' +
+ '
' +
'
Attention
' +
'
Fleet is stable
' +
'
Every visible hosted workspace currently reports a healthy posture.
' +
+ '
' +
+ '
Healthy nowAll visible hosted workspaces are clear for routine operator work.
' +
+ '
Use Team only for changeKeep roster edits explicit instead of mixing them into normal workspace work.
' +
+ '
Keep billing separateUse account services only when the task is commercial, not operational.
' +
+ '
' +
'
'
);
}
@@ -513,20 +518,20 @@ function renderAccountWorkspaceSection(account: PortalAccountSummary, accountAPI
'
' +
- '
Choose a workspace from the fleet to open its management desk.
' +
+ '
Pick a workspace to open its operator desk.
' +
'
' +
'
' +
'
Account desk
' +
- '
Keep account-wide actions close
' +
- '
Use the desk for one workspace at a time while keeping billing, team, and create-workspace actions within reach.
' +
+ '
Keep account actions close
' +
+ '
Workspace review happens here while billing, team, and create-workspace actions stay within reach.
' +
'
' + workspaceDeskActions + '
' +
'
' +
'
' +
- '
Review flow
' +
+ '
Desk flow
' +
'
' +
- '
1. Pick the workspaceSelect a fleet row to bring its health, lifecycle, and next action into this desk.
' +
- '
2. Check posture firstTreat checking or needs-attention workspaces as review work, not routine operations.
' +
- '
3. Escalate account changes separatelyBilling and team changes stay account-wide even when you are focused on one workspace.
' +
+ '
Pick the workspaceSelect a fleet row to load its health, lifecycle, and next management action.
' +
+ '
Read posture firstTreat checking or needs-attention workspaces as review work, not routine operations.
' +
+ '
Escalate account changes separatelyBilling and team changes stay account-wide even when you are focused on one workspace.
' +
'
' +
'
' +
'
' +
diff --git a/internal/cloudcp/portal/frontend/src/styles.css b/internal/cloudcp/portal/frontend/src/styles.css
index 23bcf7fe49..8be276ea33 100644
--- a/internal/cloudcp/portal/frontend/src/styles.css
+++ b/internal/cloudcp/portal/frontend/src/styles.css
@@ -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);