diff --git a/internal/cloudcp/portal/dist/build_manifest.json b/internal/cloudcp/portal/dist/build_manifest.json index c6b39b5999..238f3206da 100644 --- a/internal/cloudcp/portal/dist/build_manifest.json +++ b/internal/cloudcp/portal/dist/build_manifest.json @@ -1,5 +1,5 @@ { - "source_hash": "4b50b53b4369c0b8f2a17ddf8868a1eb8739772cb7a500616a2ba36f1449199d", + "source_hash": "d35ad29a3d34047e8332e9a38238c4feef8204c854b13a8dae9d4ea4c0cd6bfb", "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 a79298257c..a494025b43 100644 --- a/internal/cloudcp/portal/dist/portal_app.css +++ b/internal/cloudcp/portal/dist/portal_app.css @@ -1363,14 +1363,20 @@ header .logout-btn:hover, flex-direction: column; gap: 12px; } -.service-list-intro, +.service-shell-sidebar-head, .service-inline-support { padding: 14px 16px; border: 1px solid var(--line); border-radius: 12px; background: #fff; } -.service-list-intro p, +.service-shell-sidebar-head h3 { + margin: 8px 0 0; + font-size: 18px; + line-height: 1.08; + letter-spacing: -0.03em; +} +.service-shell-sidebar-head p, .service-inline-support p { margin: 8px 0 0; font-size: 13px; @@ -1429,7 +1435,7 @@ header .logout-btn:hover, grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: 12px; - padding: 14px 16px; + padding: 12px 14px; border: none; border-top: 1px solid var(--line); background: #fff; @@ -1478,14 +1484,14 @@ header .logout-btn:hover, } .service-action-copy h3 { margin: 0; - font-size: 16px; + font-size: 15px; line-height: 1.16; letter-spacing: -0.03em; } .service-action-copy p { margin: 0; - font-size: 13px; - line-height: 1.5; + font-size: 12px; + line-height: 1.45; color: var(--ink-soft); max-width: 760px; } @@ -1497,12 +1503,12 @@ header .logout-btn:hover, .service-action-highlight { display: inline-flex; align-items: center; - min-height: 24px; - padding: 0 9px; + min-height: 22px; + padding: 0 8px; border-radius: 999px; background: #fff; color: var(--ink-soft); - font-size: 10px; + font-size: 9px; font-weight: 600; } .service-action-cta { diff --git a/internal/cloudcp/portal/dist/portal_app.js b/internal/cloudcp/portal/dist/portal_app.js index a799d4fd01..2b9668ad6b 100644 --- a/internal/cloudcp/portal/dist/portal_app.js +++ b/internal/cloudcp/portal/dist/portal_app.js @@ -1878,7 +1878,7 @@ var hostedContent = accounts.map(function(account) { return '
' + renderAccountOverviewSection(account) + renderAccountWorkspaceSection(account, context.accountAPIBasePath) + renderAccountTeamSection(account) + "
"; }).join(""); - return '
' + renderShellNavigation(accounts, context.bootstrap.support_email || "", activeSection) + '
' + renderOverviewBand(accounts) + (accounts.length === 1 ? renderPrimaryAccountBar(accounts[0]) : "") + '
' + hostedContent + '

' + serviceHeading + '

' + serviceNote + '

Choose the next commercial action

Open a billing, license, refund, or privacy flow from the service navigator. The active request stays here so the account-services area behaves like one operating desk instead of a list of disconnected tools.

Start here
BillingOpen Stripe customer portal access after verification.
LicensesRecover the latest active self-hosted license and invoice link.
RefundsConfirm eligibility before revoking active commercial access.
PrivacyRequest export or deletion without leaving Pulse Account.
What to expect
Verification firstEach flow confirms the commercial email before opening sensitive account actions.
One task at a timeThe active commercial request stays in this panel until you finish or switch tools.
Support stays closeIf billing, licenses, refunds, or privacy behave unexpectedly, escalate from this surface.
Need help with billing, refund, privacy, or license actions? ' + escapeHTML(context.bootstrap.support_email || "") + '

Data and privacy

Request export or deletion of the commercial data tied to an email address. Payment data held directly by Stripe still requires support handling.

Payment-card data stays with Stripe. For Stripe deletion support, contact ' + escapeHTML(context.bootstrap.support_email || "") + '.

Use support if a billing, refund, privacy, or license flow does not behave as expected for this account.

' + escapeHTML(context.bootstrap.support_email || "") + '
' + renderSupportSection(context) + "
"; + return '
' + renderShellNavigation(accounts, context.bootstrap.support_email || "", activeSection) + '
' + renderOverviewBand(accounts) + (accounts.length === 1 ? renderPrimaryAccountBar(accounts[0]) : "") + '
' + hostedContent + '

' + serviceHeading + '

' + serviceNote + '

Choose the next commercial action

Open a billing, license, refund, or privacy flow from the service navigator. The active request stays here so the account-services area behaves like one operating desk instead of a list of disconnected tools.

Start here
BillingOpen Stripe customer portal access after verification.
LicensesRecover the latest active self-hosted license and invoice link.
RefundsConfirm eligibility before revoking active commercial access.
PrivacyRequest export or deletion without leaving Pulse Account.
What to expect
Verification firstEach flow confirms the commercial email before opening sensitive account actions.
One task at a timeThe active commercial request stays in this panel until you finish or switch tools.
Support stays closeIf billing, licenses, refunds, or privacy behave unexpectedly, escalate from this surface.
Need help with billing, refund, privacy, or license actions? ' + escapeHTML(context.bootstrap.support_email || "") + '

Data and privacy

Request export or deletion of the commercial data tied to an email address. Payment data held directly by Stripe still requires support handling.

Payment-card data stays with Stripe. For Stripe deletion support, contact ' + escapeHTML(context.bootstrap.support_email || "") + '.

Use support if a billing, refund, privacy, or license flow does not behave as expected for this account.

' + escapeHTML(context.bootstrap.support_email || "") + '
' + renderSupportSection(context) + "
"; } function renderSignedOutPortalHTML(context) { var statusHTML = ""; diff --git a/internal/cloudcp/portal/frontend/src/shell_view.test.ts b/internal/cloudcp/portal/frontend/src/shell_view.test.ts index 57bffa9143..ce105a039e 100644 --- a/internal/cloudcp/portal/frontend/src/shell_view.test.ts +++ b/internal/cloudcp/portal/frontend/src/shell_view.test.ts @@ -199,7 +199,7 @@ describe('shell view', function() { expect(html).toContain('Self-hosted only'); expect(html).toContain('portal-shell-head-title">Account console'); expect(html).toContain('Billing, license recovery, refunds, and privacy actions until hosted access is attached.'); - expect(html).toContain('Use these tools for self-hosted billing, license recovery, refunds, and privacy.'); + expect(html).toContain('Use these tools for self-hosted commercial actions.'); expect(html).toContain('Account services'); expect(html).not.toContain('Self-hosted licenses and billing'); }); diff --git a/internal/cloudcp/portal/frontend/src/shell_view.ts b/internal/cloudcp/portal/frontend/src/shell_view.ts index e5386fa1ec..6dbd24f794 100644 --- a/internal/cloudcp/portal/frontend/src/shell_view.ts +++ b/internal/cloudcp/portal/frontend/src/shell_view.ts @@ -780,9 +780,10 @@ export function renderAuthenticatedPortalHTML(context: ShellViewContext): string '' + '
' + '