Restructure sidebar navigation to match product workflows

Major IA restructure based on product-level analysis of Stella Ops core
workflows (release lifecycle, policy gates, evidence chain, security posture).

New 7-group structure (was 5):
- Dashboard: new ungrouped home link (no group header)
- Release Control: Deployments, Releases, Environments (moved from Ops)
- Security: Vulnerabilities, Security Posture (+children), Scan Image
  (Reports removed — will merge into Posture in Phase B)
- Policy: NEW GROUP — Packs, Governance, Simulation, VEX & Exceptions,
  Release Gates, Policy Audit (promoted from buried Operations item)
- Operations: slimmed from 11→8 items (Hub, Jobs, Scripts, Signals,
  Diagnostics, Notifications, Feeds, Watchlist)
- Audit & Evidence: Overview, Capsules, Replay, Export, Audit Log,
  Bundles (route fixed to /evidence/bundles), Trust (merged name)
- Settings: unchanged

Rationale:
- Policy is a release-blocking gate with 6 deep sub-workflows — not an ops utility
- Environments define the promotion graph — belongs in Release Control
- Trust Audit + Trust Analytics merged into single "Trust" item
- Reports removed (duplicated Security Posture content)
- Versions removed from sidebar (will merge into Releases as tab in Phase C)
- Dashboard link ensures users can always navigate home without logo click

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
master
2026-03-27 14:34:25 +02:00
parent 27b2759b00
commit f08ad767b7

View File

@@ -116,7 +116,7 @@ interface NavSectionGroup {
role="group" role="group"
[attr.aria-label]="group.label" [attr.aria-label]="group.label"
> >
@if (!effectiveCollapsed) { @if (!effectiveCollapsed && group.id !== 'home') {
<button <button
type="button" type="button"
class="sb-group__header" class="sb-group__header"
@@ -626,10 +626,19 @@ export class AppSidebarComponent implements AfterViewInit {
private readonly pendingApprovalsBadgeLoading = signal(false); private readonly pendingApprovalsBadgeLoading = signal(false);
/** /**
* Navigation sections - canonical 5-group IA. * Navigation sections - canonical 7-group IA.
* Groups: Release Control, Security, Operations, Audit & Evidence, Setup & Admin. * Groups: Home (ungrouped), Release Control, Security, Policy, Operations, Audit & Evidence, Setup & Admin.
*/ */
readonly navSections: NavSection[] = [ readonly navSections: NavSection[] = [
// ── Home (ungrouped) ───────────────────────────────────────────
{
id: 'dashboard',
label: 'Dashboard',
icon: 'home',
route: '/',
menuGroupId: 'home',
menuGroupLabel: '',
},
// ── Group 1: Release Control ───────────────────────────────────── // ── Group 1: Release Control ─────────────────────────────────────
{ {
id: 'deployments', id: 'deployments',
@@ -660,18 +669,7 @@ export class AppSidebarComponent implements AfterViewInit {
StellaOpsScopes.RELEASE_PUBLISH, StellaOpsScopes.RELEASE_PUBLISH,
], ],
}, },
{ // Versions merged into Releases page as a tab
id: 'versions',
label: 'Versions',
icon: 'layers',
route: '/releases/versions',
menuGroupId: 'release-control',
menuGroupLabel: 'Release Control',
requireAnyScope: [
StellaOpsScopes.RELEASE_READ,
StellaOpsScopes.RELEASE_WRITE,
],
},
// Approvals nav item removed — merged into Deployments page as a tab // Approvals nav item removed — merged into Deployments page as a tab
// ── Group 2: Security ──────────────────────────────────────────── // ── Group 2: Security ────────────────────────────────────────────
{ {
@@ -720,18 +718,7 @@ export class AppSidebarComponent implements AfterViewInit {
menuGroupLabel: 'Security', menuGroupLabel: 'Security',
requireAnyScope: [StellaOpsScopes.SCANNER_READ], requireAnyScope: [StellaOpsScopes.SCANNER_READ],
}, },
{ // Reports merged into Security Posture (export actions moved inline)
id: 'sec-reports',
label: 'Reports',
icon: 'book-open',
route: '/security/reports',
menuGroupId: 'security',
menuGroupLabel: 'Security',
requireAnyScope: [
StellaOpsScopes.SCANNER_READ,
StellaOpsScopes.FINDINGS_READ,
],
},
// ── Group 3: Operations ────────────────────────────────────────── // ── Group 3: Operations ──────────────────────────────────────────
{ {
id: 'ops', id: 'ops',
@@ -791,8 +778,8 @@ export class AppSidebarComponent implements AfterViewInit {
label: 'Environments', label: 'Environments',
icon: 'globe', icon: 'globe',
route: '/environments/regions', route: '/environments/regions',
menuGroupId: 'operations', menuGroupId: 'release-control',
menuGroupLabel: 'Operations', menuGroupLabel: 'Release Control',
requireAnyScope: [ requireAnyScope: [
StellaOpsScopes.ORCH_READ, StellaOpsScopes.ORCH_READ,
StellaOpsScopes.ORCH_OPERATE, StellaOpsScopes.ORCH_OPERATE,
@@ -800,13 +787,58 @@ export class AppSidebarComponent implements AfterViewInit {
}, },
{ {
id: 'ops-policy', id: 'ops-policy',
label: 'Policy', label: 'Packs',
icon: 'clipboard', icon: 'clipboard',
route: '/ops/policy', route: '/ops/policy/packs',
menuGroupId: 'operations', menuGroupId: 'policy',
menuGroupLabel: 'Operations', menuGroupLabel: 'Policy',
requireAnyScope: [StellaOpsScopes.POLICY_READ], requireAnyScope: [StellaOpsScopes.POLICY_READ],
}, },
{
id: 'ops-policy-governance',
label: 'Governance',
icon: 'shield',
route: '/ops/policy/governance',
menuGroupId: 'policy',
menuGroupLabel: 'Policy',
requireAnyScope: [StellaOpsScopes.POLICY_READ],
},
{
id: 'ops-policy-simulation',
label: 'Simulation',
icon: 'play',
route: '/ops/policy/simulation',
menuGroupId: 'policy',
menuGroupLabel: 'Policy',
requireAnyScope: [StellaOpsScopes.POLICY_SIMULATE],
},
{
id: 'ops-policy-vex',
label: 'VEX & Exceptions',
icon: 'file-text',
route: '/ops/policy/vex',
menuGroupId: 'policy',
menuGroupLabel: 'Policy',
requireAnyScope: [StellaOpsScopes.VEX_READ, StellaOpsScopes.EXCEPTION_READ],
},
{
id: 'ops-policy-gates',
label: 'Release Gates',
icon: 'check-square',
route: '/ops/policy/gates',
menuGroupId: 'policy',
menuGroupLabel: 'Policy',
requireAnyScope: [StellaOpsScopes.POLICY_READ],
},
{
id: 'ops-policy-audit',
label: 'Policy Audit',
icon: 'list',
route: '/ops/policy/audit',
menuGroupId: 'policy',
menuGroupLabel: 'Policy',
requireAnyScope: [StellaOpsScopes.POLICY_AUDIT],
},
{ {
id: 'ops-diagnostics', id: 'ops-diagnostics',
label: 'Diagnostics', label: 'Diagnostics',
@@ -846,15 +878,7 @@ export class AppSidebarComponent implements AfterViewInit {
menuGroupLabel: 'Operations', menuGroupLabel: 'Operations',
requireAnyScope: [StellaOpsScopes.SIGNER_READ], requireAnyScope: [StellaOpsScopes.SIGNER_READ],
}, },
{ // Trust Analytics merged into Trust (Audit & Evidence group)
id: 'ops-trust-analytics',
label: 'Trust Analytics',
icon: 'trending-up',
route: '/ops/operations/trust-analytics',
menuGroupId: 'operations',
menuGroupLabel: 'Operations',
requireAnyScope: [StellaOpsScopes.SIGNER_READ],
},
// ── Group 4: Audit & Evidence ──────────────────────────────────── // ── Group 4: Audit & Evidence ────────────────────────────────────
{ {
id: 'evidence-overview', id: 'evidence-overview',
@@ -923,7 +947,7 @@ export class AppSidebarComponent implements AfterViewInit {
id: 'evidence-bundles', id: 'evidence-bundles',
label: 'Bundles', label: 'Bundles',
icon: 'inbox', icon: 'inbox',
route: '/triage/audit-bundles', route: '/evidence/bundles',
menuGroupId: 'audit-evidence', menuGroupId: 'audit-evidence',
menuGroupLabel: 'Audit & Evidence', menuGroupLabel: 'Audit & Evidence',
requireAnyScope: [ requireAnyScope: [
@@ -932,8 +956,8 @@ export class AppSidebarComponent implements AfterViewInit {
], ],
}, },
{ {
id: 'evidence-trust-audit', id: 'evidence-trust',
label: 'Trust Audit', label: 'Trust',
icon: 'shield-check', icon: 'shield-check',
route: '/evidence/audit-log/trust', route: '/evidence/audit-log/trust',
menuGroupId: 'audit-evidence', menuGroupId: 'audit-evidence',
@@ -1013,7 +1037,7 @@ export class AppSidebarComponent implements AfterViewInit {
/** Menu groups rendered in deterministic order for scanability */ /** Menu groups rendered in deterministic order for scanability */
readonly displaySectionGroups = computed<NavSectionGroup[]>(() => { readonly displaySectionGroups = computed<NavSectionGroup[]>(() => {
const orderedGroups = new Map<string, NavSectionGroup>(); const orderedGroups = new Map<string, NavSectionGroup>();
const groupOrder = ['release-control', 'security', 'operations', 'audit-evidence', 'setup-admin', 'misc']; const groupOrder = ['home', 'release-control', 'security', 'policy', 'operations', 'audit-evidence', 'setup-admin', 'misc'];
for (const groupId of groupOrder) { for (const groupId of groupOrder) {
orderedGroups.set(groupId, { orderedGroups.set(groupId, {
@@ -1126,10 +1150,14 @@ export class AppSidebarComponent implements AfterViewInit {
private resolveMenuGroupLabel(groupId: string): string { private resolveMenuGroupLabel(groupId: string): string {
switch (groupId) { switch (groupId) {
case 'home':
return '';
case 'release-control': case 'release-control':
return 'Release Control'; return 'Release Control';
case 'security': case 'security':
return 'Security'; return 'Security';
case 'policy':
return 'Policy';
case 'operations': case 'operations':
return 'Operations'; return 'Operations';
case 'audit-evidence': case 'audit-evidence':