mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-11 14:00:29 +00:00
Make self-hosted portal nav honest
This commit is contained in:
@@ -159,7 +159,8 @@ Core rules:
|
||||
current account set, not a repeated per-account dashboard.
|
||||
17. When `Workspaces` or `Access` are unavailable, the tab must show an
|
||||
explicit unavailable state that points to the right live task instead of
|
||||
rendering blank space.
|
||||
rendering blank space. The shell navigation must stay honest too: it must
|
||||
not badge or describe unavailable hosted tasks as though they are live.
|
||||
18. `Access` must stay action-first: roster, invite, role change, and remove
|
||||
access are the job; view-only users may review the roster but must never
|
||||
see live controls that imply they can mutate it.
|
||||
|
||||
@@ -293,7 +293,9 @@ self-hosted commercial history is relevant to the signed-in account, so
|
||||
hosted-only accounts do not render self-hosted license, refund, privacy, or
|
||||
self-hosted escalation paths by default, and self-hosted-only accounts do not
|
||||
front-load an empty hosted-billing block before the real self-hosted jobs.
|
||||
`Support` follows the same
|
||||
The same account-shape runtime contract must also keep the shell navigation
|
||||
honest: unavailable hosted `Workspaces` or `Access` tasks must read as
|
||||
unavailable in the nav itself rather than implying live hosted work. `Support` follows the same
|
||||
account-shape runtime contract: self-hosted-only accounts expose only the
|
||||
billing escalation path and billing-specific handoff packet, and hosted
|
||||
workspace/access escalation controls must not render when no hosted account
|
||||
|
||||
@@ -827,7 +827,9 @@ standalone Relay portal. That shell now also owns two product rules
|
||||
explicitly: `Overview` is one shell-level triage surface across the current
|
||||
account set rather than a repeated per-account dashboard, and unavailable
|
||||
top-level `Workspaces` or `Access` tasks must render an explicit unavailable
|
||||
state instead of blank space. The same shell also owns action-first task
|
||||
state instead of blank space. The shell navigation must stay honest to that
|
||||
same account shape rather than labelling unavailable hosted tasks as though
|
||||
they are still live. The same shell also owns action-first task
|
||||
surfaces for `Access`, `Billing`, and `Support`: access mutations must be
|
||||
permission-honest and roster-led, billing must reduce to one obvious job at a
|
||||
time with hosted billing first when relevant, support must stay a failed-path
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"source_hash": "f417480ba4d79a766c8bb7511a50e02f825cb2199eb6961c618b5a9429d63975",
|
||||
"source_hash": "176432adedcb3aa8f1e151678f7e93e3db683ef6029b8d6b60a04264e2bff8fd",
|
||||
"build_inputs": [
|
||||
"package.json",
|
||||
"tsconfig.json",
|
||||
|
||||
+2
-1
@@ -2038,6 +2038,7 @@
|
||||
}
|
||||
function renderShellNavigation(accounts, supportEmail, activeSection) {
|
||||
var hosted = hasHostedAccounts(accounts);
|
||||
var selfHostedOnly = !hosted;
|
||||
var workspaces = collectWorkspaces(accounts);
|
||||
var totalWorkspaces = workspaces.length;
|
||||
var readyWorkspaces = countReadyWorkspaces(workspaces);
|
||||
@@ -2052,7 +2053,7 @@
|
||||
hostedBillingCount += 1;
|
||||
}
|
||||
}
|
||||
return '<aside class="portal-shell-nav" aria-label="Pulse Account sections"><div class="portal-shell-nav-header"><div class="portal-shell-nav-eyebrow">Pulse Account</div><div class="portal-shell-nav-title">Account tasks</div><div class="portal-shell-nav-support">' + (hosted ? "Start with the job you need to finish: workspace work, access, billing, then escalation." : "Use billing tools first and escalate only when the self-serve path stops.") + '</div></div><div class="portal-shell-nav-group">' + shellSectionButton("overview", activeSection, "01", "Overview", "What needs attention, what is ready, and the next obvious action.", attentionCount > 0 ? String(attentionCount) + " review" : hosted ? String(readyWorkspaces) + " ready" : "Summary") + shellSectionButton("workspaces", activeSection, "02", "Workspaces", hosted ? "Open a workspace, review lifecycle state, or create one." : "No hosted workspaces are attached yet.", hosted ? String(readyWorkspaces) + " ready" : "None") + shellSectionButton("access", activeSection, "03", "Access", hosted ? "Invite people, change roles, and remove account access." : "Account access and membership controls.", canManage ? "Manage" : "View") + shellSectionButton("billing", activeSection, "04", "Billing", hostedBillingCount > 0 ? "Hosted billing first, then self-hosted licenses, refunds, and privacy only when relevant." : "Self-hosted billing, licenses, refunds, and privacy.", hostedBillingCount > 0 ? hostedBillingCount > 1 ? "Hosted +" : "Hosted" : "Self-hosted") + shellSectionButton("support", activeSection, "05", "Support", "Escalation only after the workspace, access, or billing path is exhausted.", supportEmail ? "Email" : "Help") + "</div></aside>";
|
||||
return '<aside class="portal-shell-nav" aria-label="Pulse Account sections"><div class="portal-shell-nav-header"><div class="portal-shell-nav-eyebrow">Pulse Account</div><div class="portal-shell-nav-title">Account tasks</div><div class="portal-shell-nav-support">' + (hosted ? "Start with the job you need to finish: workspace work, access, billing, then escalation." : "Use billing tools first and escalate only when the self-serve path stops.") + '</div></div><div class="portal-shell-nav-group">' + shellSectionButton("overview", activeSection, "01", "Overview", "What needs attention, what is ready, and the next obvious action.", attentionCount > 0 ? String(attentionCount) + " review" : hosted ? String(readyWorkspaces) + " ready" : "Summary") + shellSectionButton("workspaces", activeSection, "02", "Workspaces", hosted ? "Open a workspace, review lifecycle state, or create one." : "Unavailable on this account. Hosted workspaces are not attached here.", hosted ? String(readyWorkspaces) + " ready" : "Unavailable") + shellSectionButton("access", activeSection, "03", "Access", hosted ? "Invite people, change roles, and remove account access." : "Unavailable on this account. Hosted roster and role controls live only on hosted workspace accounts.", hosted ? canManage ? "Manage" : "View" : "Unavailable") + shellSectionButton("billing", activeSection, "04", "Billing", hostedBillingCount > 0 ? "Hosted billing first, then self-hosted licenses, refunds, and privacy only when relevant." : "Self-hosted billing, licenses, refunds, and privacy.", hostedBillingCount > 0 ? hostedBillingCount > 1 ? "Hosted +" : "Hosted" : "Self-hosted") + shellSectionButton("support", activeSection, "05", "Support", selfHostedOnly ? "Escalation only after the billing path is exhausted." : "Escalation only after the workspace, access, or billing path is exhausted.", supportEmail ? "Email" : "Help") + "</div></aside>";
|
||||
}
|
||||
function renderWorkspaceCard(account, workspace, accountAPIBasePath) {
|
||||
var status = workspaceHealthState(workspace);
|
||||
|
||||
@@ -397,6 +397,11 @@ describe('shell view', function() {
|
||||
expect(html).toContain('Account tasks');
|
||||
expect(html).toContain('Self-hosted');
|
||||
expect(html).toContain('Account triage');
|
||||
expect(html).toMatch(/Workspaces[\s\S]*Unavailable on this account\. Hosted workspaces are not attached here\./);
|
||||
expect(html).toContain('Unavailable on this account. Hosted workspaces are not attached here.');
|
||||
expect(html).toMatch(/Access[\s\S]*Unavailable on this account\. Hosted roster and role controls live only on hosted workspace accounts\./);
|
||||
expect(html).toContain('Unavailable on this account. Hosted roster and role controls live only on hosted workspace accounts.');
|
||||
expect(html).toMatch(/Support[\s\S]*Escalation only after the billing path is exhausted\./);
|
||||
expect(html).toContain('No hosted account');
|
||||
expect(html).toContain('Billing tools are ready');
|
||||
expect(html).toContain('There is nothing to open or manage here yet.');
|
||||
|
||||
@@ -360,6 +360,7 @@ function shellSectionButton(section: PortalShellSection, activeSection: PortalSh
|
||||
|
||||
function renderShellNavigation(accounts: PortalAccountSummary[], supportEmail: string, activeSection: PortalShellSection): string {
|
||||
var hosted = hasHostedAccounts(accounts);
|
||||
var selfHostedOnly = !hosted;
|
||||
var workspaces = collectWorkspaces(accounts);
|
||||
var totalWorkspaces = workspaces.length;
|
||||
var readyWorkspaces = countReadyWorkspaces(workspaces);
|
||||
@@ -379,14 +380,14 @@ function renderShellNavigation(accounts: PortalAccountSummary[], supportEmail: s
|
||||
'<div class="portal-shell-nav-header">' +
|
||||
'<div class="portal-shell-nav-eyebrow">Pulse Account</div>' +
|
||||
'<div class="portal-shell-nav-title">Account tasks</div>' +
|
||||
'<div class="portal-shell-nav-support">' + (hosted ? 'Start with the job you need to finish: workspace work, access, billing, then escalation.' : 'Use billing tools first and escalate only when the self-serve path stops.' ) + '</div>' +
|
||||
'<div class="portal-shell-nav-support">' + (hosted ? 'Start with the job you need to finish: workspace work, access, billing, then escalation.' : 'Use billing tools first and escalate only when the self-serve path stops.') + '</div>' +
|
||||
'</div>' +
|
||||
'<div class="portal-shell-nav-group">' +
|
||||
shellSectionButton('overview', activeSection, '01', 'Overview', 'What needs attention, what is ready, and the next obvious action.', attentionCount > 0 ? String(attentionCount) + ' review' : (hosted ? String(readyWorkspaces) + ' ready' : 'Summary')) +
|
||||
shellSectionButton('workspaces', activeSection, '02', 'Workspaces', hosted ? 'Open a workspace, review lifecycle state, or create one.' : 'No hosted workspaces are attached yet.', hosted ? String(readyWorkspaces) + ' ready' : 'None') +
|
||||
shellSectionButton('access', activeSection, '03', 'Access', hosted ? 'Invite people, change roles, and remove account access.' : 'Account access and membership controls.', canManage ? 'Manage' : 'View') +
|
||||
shellSectionButton('workspaces', activeSection, '02', 'Workspaces', hosted ? 'Open a workspace, review lifecycle state, or create one.' : 'Unavailable on this account. Hosted workspaces are not attached here.', hosted ? String(readyWorkspaces) + ' ready' : 'Unavailable') +
|
||||
shellSectionButton('access', activeSection, '03', 'Access', hosted ? 'Invite people, change roles, and remove account access.' : 'Unavailable on this account. Hosted roster and role controls live only on hosted workspace accounts.', hosted ? (canManage ? 'Manage' : 'View') : 'Unavailable') +
|
||||
shellSectionButton('billing', activeSection, '04', 'Billing', hostedBillingCount > 0 ? 'Hosted billing first, then self-hosted licenses, refunds, and privacy only when relevant.' : 'Self-hosted billing, licenses, refunds, and privacy.', hostedBillingCount > 0 ? (hostedBillingCount > 1 ? 'Hosted +' : 'Hosted') : 'Self-hosted') +
|
||||
shellSectionButton('support', activeSection, '05', 'Support', 'Escalation only after the workspace, access, or billing path is exhausted.', supportEmail ? 'Email' : 'Help') +
|
||||
shellSectionButton('support', activeSection, '05', 'Support', selfHostedOnly ? 'Escalation only after the billing path is exhausted.' : 'Escalation only after the workspace, access, or billing path is exhausted.', supportEmail ? 'Email' : 'Help') +
|
||||
'</div>' +
|
||||
'</aside>'
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user