Tighten hosted portal shell spacing

This commit is contained in:
rcourtman
2026-04-01 20:32:22 +01:00
parent 215494c7ac
commit f6846443f3
8 changed files with 51 additions and 32 deletions
@@ -183,6 +183,12 @@ Own canonical runtime payload shapes between backend and frontend.
26. Keep storage chart identity canonical on that same shared API surface: the shared storage charts endpoint must key pool and physical-disk series by the resolved unified-resource `MetricsTarget.ResourceID`, not by canonical resource IDs or page-local aliases, so storage rows, focused summary cards, sticky summary shells, and detail charts all address the same history series in live and mock mode.
27. Keep synthetic summary-chart fallback identity canonical on that same shared API surface: when `internal/api/router.go` has to synthesize mock summary history for infrastructure, workloads, or storage cards, it must derive the fallback from canonical `resourceType`, `resourceID`, and `metricType` ownership instead of raw min/max seed-prefix helpers, so range changes and runtime mock updates stay on one governed timeline.
28. Keep workload-chart response identity canonical on that same shared API surface: `internal/api/router.go`, `internal/api/contract_test.go`, and workload summary consumers must emit provider-backed VM and system-container series under the same canonical workload IDs that workloads page rows use, while resolving history through the unified `MetricsTarget.ResourceID`, so hover and focus selection do not fall off for provider-backed rows.
29. Keep the hosted account portal bootstrap intelligible without duplicate
chrome. `internal/cloudcp/portal/page.go`, the maintained portal frontend
bundle, and the shared portal styles may refine layout density, but the
account/billing shell must remain understandable from the primary header,
section title, and factual body content alone instead of depending on a
second context-chip strip to restate the same scope.
## Forbidden Paths
@@ -110,6 +110,11 @@ agreement, and cloud-specific enforcement rules.
19. Add or change cloud plan presentation through `frontend-modern/src/pages/CloudPricing.tsx`
20. Add contract tests where runtime and pricing need to stay aligned
21. Add or change hosted browser org-context bootstrap through `frontend-modern/src/App.tsx`, `frontend-modern/src/AppLayout.tsx`, `frontend-modern/src/useAppRuntimeState.ts`, and `frontend-modern/src/utils/apiClient.ts`
22. Keep the hosted account portal shell task-first and compact. Section
headers, billing action rows, and the maintained portal bundle under
`internal/cloudcp/portal/` may surface the facts an operator needs, but the
shell should not spend vertical space on duplicate context-chip strips when
the page header and section body already communicate that scope.
## Forbidden Paths
+1 -1
View File
@@ -1,5 +1,5 @@
{
"source_hash": "dee5325d02ca13ee1eb3b4ff7d09478406bbd5805999ced971f4b7f62700d7f0",
"source_hash": "8cda796f9a573443304df3d61c11f26041c8cd7f050fc737b67e3c056cd09b6c",
"build_inputs": [
"package.json",
"tsconfig.json",
+16 -9
View File
@@ -81,6 +81,7 @@
--warn: #9a6700;
--warn-subtle: #fff8c5;
--radius: 6px;
--header-height: 49px;
}
*,
*::before,
@@ -89,6 +90,7 @@
}
html {
scroll-behavior: smooth;
background: var(--bg);
}
body {
margin: 0;
@@ -118,15 +120,16 @@ header {
align-items: center;
justify-content: space-between;
gap: 12px;
height: 48px;
padding: 0 16px;
height: var(--header-height);
padding: 0 24px;
border-bottom: 1px solid var(--border);
background: var(--bg);
background: var(--bg-subtle);
}
header .brand {
font-size: 14px;
font-weight: 600;
color: var(--ink);
white-space: nowrap;
}
.header-account-chip {
display: inline-flex;
@@ -204,9 +207,9 @@ header .logout-btn:hover,
font-size: 12px;
}
.main {
max-width: 800px;
max-width: 960px;
margin: 0 auto;
padding: 0 24px 64px;
padding: 0 32px 64px;
}
.portal-shell {
display: flex;
@@ -217,13 +220,13 @@ header .logout-btn:hover,
display: flex;
align-items: baseline;
gap: 8px;
padding: 16px 0 12px;
padding: 16px 0 0;
font-size: 13px;
color: var(--ink-secondary);
}
.portal-identity-bar h2 {
margin: 0;
font-size: 20px;
font-size: 18px;
font-weight: 600;
color: var(--ink);
}
@@ -234,7 +237,8 @@ header .logout-btn:hover,
display: flex;
align-items: stretch;
gap: 0;
border-bottom: 1px solid var(--border);
border-bottom: 1px solid var(--border-muted);
margin-top: 8px;
margin-bottom: 0;
}
.portal-tab {
@@ -293,7 +297,7 @@ header .logout-btn:hover,
display: none !important;
}
.portal-content-panel {
padding: 20px 0 0;
padding: 16px 0 0;
}
.portal-section-header {
display: flex;
@@ -1591,6 +1595,9 @@ header .logout-btn:hover,
background: #82181a;
}
@media (max-width: 768px) {
header {
padding: 0 16px;
}
.main {
padding: 0 16px 48px;
}
+1 -4
View File
@@ -2062,10 +2062,7 @@
return '<article class="billing-action-row"><div class="billing-action-main"><div class="billing-action-copy"><h3>' + title + "</h3><p>" + description + '</p></div><div class="billing-action-meta">' + meta + '</div></div><div class="billing-action-cta"><button class="btn-secondary billing-action-button" type="button" id="' + id + '" data-account-billing-action="open-billing-panel" data-account-billing-panel="' + panelID + '" data-account-billing-focus="' + focusID + '" data-shell-target="billing">' + escapeHTML(actionLabel) + "</button></div></article>";
}
function renderSectionContextChips(chips) {
if (!chips.length) return "";
return '<div class="section-context-strip">' + chips.map(function(chip) {
return '<span class="section-context-chip">' + escapeHTML(chip) + "</span>";
}).join("") + "</div>";
return "";
}
function attentionWorkspaces(workspaces) {
var results = [];
@@ -192,6 +192,9 @@ describe('shell view', function() {
expect(html).not.toContain('>Licenses<');
expect(html).not.toContain('>Refunds<');
expect(html).not.toContain('>Privacy<');
expect(html).not.toContain('section-context-strip');
expect(html).not.toContain('View roster');
expect(html).not.toContain('Owner or admin required');
});
it('defaults the authenticated hosted shell to workspaces', function() {
@@ -217,10 +217,7 @@ function renderBillingActionRow(
}
function renderSectionContextChips(chips: string[]): string {
if (!chips.length) return '';
return '<div class="section-context-strip">' + chips.map(function(chip) {
return '<span class="section-context-chip">' + escapeHTML(chip) + '</span>';
}).join('') + '</div>';
return '';
}
function renderFactLine(className: string, facts: string[]): string {
+18 -14
View File
@@ -38,11 +38,12 @@
--warn: #9a6700;
--warn-subtle: #fff8c5;
--radius: 6px;
--header-height: 49px;
}
/* ── Reset ────────────────────────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; }
html { scroll-behavior: smooth; }
html { scroll-behavior: smooth; background: var(--bg); }
body {
margin: 0;
@@ -59,7 +60,7 @@ a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }
[hidden] { display: none !important; }
/* ── Header ───────────────────────────────────────────────────── */
/* ── Header (top bar) ─────────────────────────────────────────── */
header {
position: sticky;
top: 0;
@@ -68,16 +69,17 @@ header {
align-items: center;
justify-content: space-between;
gap: 12px;
height: 48px;
padding: 0 16px;
height: var(--header-height);
padding: 0 24px;
border-bottom: 1px solid var(--border);
background: var(--bg);
background: var(--bg-subtle);
}
header .brand {
font-size: 14px;
font-weight: 600;
color: var(--ink);
white-space: nowrap;
}
.header-account-chip {
@@ -158,9 +160,9 @@ header .logout-btn:hover,
/* ── Main container ───────────────────────────────────────────── */
.main {
max-width: 800px;
max-width: 960px;
margin: 0 auto;
padding: 0 24px 64px;
padding: 0 32px 64px;
}
/* ── Portal shell (signed-in) ─────────────────────────────────── */
@@ -170,19 +172,19 @@ header .logout-btn:hover,
gap: 0;
}
/* Identity bar — tighter, no padding top */
/* ── Identity bar — compact page header ───────────────────────── */
.portal-identity-bar {
display: flex;
align-items: baseline;
gap: 8px;
padding: 16px 0 12px;
padding: 16px 0 0;
font-size: 13px;
color: var(--ink-secondary);
}
.portal-identity-bar h2 {
margin: 0;
font-size: 20px;
font-size: 18px;
font-weight: 600;
color: var(--ink);
}
@@ -191,12 +193,13 @@ header .logout-btn:hover,
color: var(--border);
}
/* Tab navigation */
/* ── Tab navigation ───────────────────────────────────────────── */
.portal-tab-bar {
display: flex;
align-items: stretch;
gap: 0;
border-bottom: 1px solid var(--border);
border-bottom: 1px solid var(--border-muted);
margin-top: 8px;
margin-bottom: 0;
}
@@ -229,7 +232,7 @@ header .logout-btn:hover,
font-weight: 400;
}
/* Content panels — hide/show logic */
/* ── Content panels — hide/show logic ─────────────────────────── */
.portal-shell-main {
display: flex;
flex-direction: column;
@@ -263,7 +266,7 @@ header .logout-btn:hover,
}
.portal-content-panel {
padding: 20px 0 0;
padding: 16px 0 0;
}
/* ── Section headers ──────────────────────────────────────────── */
@@ -1306,6 +1309,7 @@ header .logout-btn:hover,
/* ── Responsive ───────────────────────────────────────────────── */
@media (max-width: 768px) {
header { padding: 0 16px; }
.main { padding: 0 16px 48px; }
.portal-tab-bar { overflow-x: auto; scrollbar-width: none; gap: 0; }