Extend stella-quick-links with aside layout + descriptions, standardize usage
Component extension: - Add 'description' field to StellaQuickLink interface - Add 'layout' input: 'inline' (default, horizontal dots) or 'aside' (vertical with descriptions, border-left accent on hover) - Aside layout shows label + description per link in a vertical list - Full CSS for aside variant: hover states, focus ring, icon transitions Dashboard page: - Quick Links moved to aside panel with elevated background and border - All 6 links now have descriptions (e.g., "Deployment timeline and run history") Evidence Overview page: - "Shortcuts" and "Related Domains" sections use layout="aside" - All 10 links now have descriptions AGENTS.md: - New "Quick Links Convention (MANDATORY)" rule: must use stella-quick-links with layout="aside", descriptions, and right-aligned aside placement Remaining pages (8+) to be updated in follow-up commit. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -141,6 +141,31 @@ The Promote button on release detail pages must follow a three-state model:
|
|||||||
Use `showPromote` (computed, boolean) for visibility and `canPromote` (computed, boolean) for the enabled/disabled state.
|
Use `showPromote` (computed, boolean) for visibility and `canPromote` (computed, boolean) for the enabled/disabled state.
|
||||||
Use `promoteDisabledReason` (computed, string | null) for the disabled tooltip.
|
Use `promoteDisabledReason` (computed, string | null) for the disabled tooltip.
|
||||||
|
|
||||||
|
## Quick Links Convention (MANDATORY)
|
||||||
|
|
||||||
|
All pages that include navigational quick links **must** follow these rules:
|
||||||
|
|
||||||
|
1. Use the `<stella-quick-links>` component — never raw `<nav>` with dot separators
|
||||||
|
2. Pass `layout="aside"` for page-level quick links (use `inline` only for inline/contextual links)
|
||||||
|
3. Include a `description` for every link explaining what the user will find there
|
||||||
|
4. Place quick links in a right-aligned aside panel with a border and elevated background
|
||||||
|
5. Include a `label` (e.g., "Related", "Quick Links", "Shortcuts")
|
||||||
|
|
||||||
|
**Pattern:**
|
||||||
|
```html
|
||||||
|
<aside class="page-aside">
|
||||||
|
<stella-quick-links
|
||||||
|
[links]="quickLinks"
|
||||||
|
label="Quick Links"
|
||||||
|
layout="aside" />
|
||||||
|
</aside>
|
||||||
|
```
|
||||||
|
|
||||||
|
Each link must have `label`, `route`, and `description`:
|
||||||
|
```typescript
|
||||||
|
{ label: 'Security Posture', route: '/security', description: 'Risk posture and advisory freshness' }
|
||||||
|
```
|
||||||
|
|
||||||
## Metric / KPI Cards Convention (MANDATORY)
|
## Metric / KPI Cards Convention (MANDATORY)
|
||||||
|
|
||||||
All metric badges, stat cards, KPI tiles, and summary indicators **must** use `<stella-metric-card>`.
|
All metric badges, stat cards, KPI tiles, and summary indicators **must** use `<stella-metric-card>`.
|
||||||
|
|||||||
@@ -453,8 +453,10 @@ interface PendingAction {
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 6. Quick Links -->
|
<!-- 6. Quick Links (right aside) -->
|
||||||
<stella-quick-links [links]="dashboardQuickLinks" label="Quick Links" />
|
<aside class="dashboard-aside">
|
||||||
|
<stella-quick-links [links]="dashboardQuickLinks" label="Quick Links" layout="aside" />
|
||||||
|
</aside>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
`,
|
`,
|
||||||
@@ -1112,6 +1114,14 @@ interface PendingAction {
|
|||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dashboard-aside {
|
||||||
|
margin-top: 1rem;
|
||||||
|
border: 1px solid var(--color-border-primary);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
padding: 0.75rem 0;
|
||||||
|
background: var(--color-surface-elevated);
|
||||||
|
}
|
||||||
|
|
||||||
/* Status dot (reused in summary cards) */
|
/* Status dot (reused in summary cards) */
|
||||||
.status-dot {
|
.status-dot {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
@@ -1372,12 +1382,12 @@ export class DashboardV3Component implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
readonly riskTableAtTop = signal(true);
|
readonly riskTableAtTop = signal(true);
|
||||||
|
|
||||||
readonly dashboardQuickLinks: StellaQuickLink[] = [
|
readonly dashboardQuickLinks: StellaQuickLink[] = [
|
||||||
{ label: 'Release Runs', route: '/releases/runs' },
|
{ label: 'Release Runs', route: '/releases/runs', description: 'Deployment timeline and run history' },
|
||||||
{ label: 'Security & Risk', route: '/security' },
|
{ label: 'Security & Risk', route: '/security', description: 'Posture, findings, and reachability' },
|
||||||
{ label: 'Operations', route: '/ops/operations' },
|
{ label: 'Operations', route: '/ops/operations', description: 'Platform health and execution control' },
|
||||||
{ label: 'Evidence', route: '/evidence' },
|
{ label: 'Evidence', route: '/evidence', description: 'Decision capsules and audit trail' },
|
||||||
{ label: 'Platform Setup', route: '/ops/platform-setup' },
|
{ label: 'Platform Setup', route: '/ops/platform-setup', description: 'Environments, integrations, topology' },
|
||||||
{ label: 'Diagnostics', route: '/ops/operations/doctor' },
|
{ label: 'Diagnostics', route: '/ops/operations/doctor', description: 'Run health checks on your deployment' },
|
||||||
];
|
];
|
||||||
|
|
||||||
// -- Loading states -------------------------------------------------------
|
// -- Loading states -------------------------------------------------------
|
||||||
|
|||||||
@@ -109,10 +109,9 @@ type EvidenceHomeMode = 'normal' | 'degraded' | 'empty';
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="shortcuts-section" aria-label="Evidence home shortcuts">
|
<aside class="evidence-aside" aria-label="Evidence shortcuts">
|
||||||
<h2 class="section-title">Shortcuts</h2>
|
<stella-quick-links [links]="shortcutLinks" label="Shortcuts" layout="aside" />
|
||||||
<stella-quick-links [links]="shortcutLinks" />
|
</aside>
|
||||||
</section>
|
|
||||||
|
|
||||||
<!-- Quick Stats (auditor detail: audit events, proof chains) -->
|
<!-- Quick Stats (auditor detail: audit events, proof chains) -->
|
||||||
<section class="stats-section" aria-label="Evidence statistics">
|
<section class="stats-section" aria-label="Evidence statistics">
|
||||||
@@ -136,11 +135,9 @@ type EvidenceHomeMode = 'normal' | 'degraded' | 'empty';
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- Cross-domain links -->
|
<aside class="evidence-aside" aria-label="Related domains">
|
||||||
<section class="cross-links" aria-label="Related domain links">
|
<stella-quick-links [links]="relatedDomainLinks" label="Related" layout="aside" />
|
||||||
<h2 class="section-title">Related Domains</h2>
|
</aside>
|
||||||
<stella-quick-links [links]="relatedDomainLinks" />
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<!-- Trust ownership note -->
|
<!-- Trust ownership note -->
|
||||||
<aside class="ownership-note" role="note">
|
<aside class="ownership-note" role="note">
|
||||||
@@ -443,19 +440,19 @@ export class EvidenceAuditOverviewComponent {
|
|||||||
readonly mode = signal<EvidenceHomeMode>('normal');
|
readonly mode = signal<EvidenceHomeMode>('normal');
|
||||||
|
|
||||||
readonly shortcutLinks: StellaQuickLink[] = [
|
readonly shortcutLinks: StellaQuickLink[] = [
|
||||||
{ label: 'Audit Log', route: '/evidence/audit-log' },
|
{ label: 'Audit Log', route: '/evidence/audit-log', description: 'Cross-module audit trail for compliance' },
|
||||||
{ label: 'Export Center', route: '/evidence/exports' },
|
{ label: 'Export Center', route: '/evidence/exports', description: 'Export profiles and StellaBundle generation' },
|
||||||
{ label: 'Evidence Bundles', route: '/releases/bundles' },
|
{ label: 'Evidence Bundles', route: '/releases/bundles', description: 'Sealed evidence bundles for auditors' },
|
||||||
{ label: 'Replay & Verify', route: '/evidence/verify-replay' },
|
{ label: 'Replay & Verify', route: '/evidence/verify-replay', description: 'Deterministic replay of past decisions' },
|
||||||
{ label: 'Proof Chains', route: '/evidence/capsules' },
|
{ label: 'Proof Chains', route: '/evidence/capsules', description: 'Signed decision capsules with evidence' },
|
||||||
{ label: 'Trust & Signing', route: '/setup/trust-signing' },
|
{ label: 'Trust & Signing', route: '/setup/trust-signing', description: 'Signing keys and certificate management' },
|
||||||
];
|
];
|
||||||
|
|
||||||
readonly relatedDomainLinks: StellaQuickLink[] = [
|
readonly relatedDomainLinks: StellaQuickLink[] = [
|
||||||
{ label: 'Release Control', route: '/releases/runs', hint: 'Evidence attached to releases and promotions' },
|
{ label: 'Release Control', route: '/releases/runs', description: 'Evidence attached to releases and promotions' },
|
||||||
{ label: 'Trust & Signing', route: '/setup/trust-signing', hint: 'Key management and signing policy' },
|
{ label: 'Trust & Signing', route: '/setup/trust-signing', description: 'Key management and signing policy' },
|
||||||
{ label: 'Policy Governance', route: '/ops/policy/governance', hint: 'Policy packs driving evidence requirements' },
|
{ label: 'Policy Governance', route: '/ops/policy/governance', description: 'Policy packs driving evidence requirements' },
|
||||||
{ label: 'Findings', route: '/security/findings', hint: 'Findings linked to evidence records' },
|
{ label: 'Findings', route: '/security/findings', description: 'Findings linked to evidence records' },
|
||||||
];
|
];
|
||||||
|
|
||||||
readonly quickViews = computed((): EvidenceQuickViewTile[] => {
|
readonly quickViews = computed((): EvidenceQuickViewTile[] => {
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ export interface StellaQuickLink {
|
|||||||
icon?: string;
|
icon?: string;
|
||||||
/** Tooltip text */
|
/** Tooltip text */
|
||||||
hint?: string;
|
hint?: string;
|
||||||
|
/** Short description shown below the label in aside layout */
|
||||||
|
description?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@@ -36,32 +38,59 @@ export interface StellaQuickLink {
|
|||||||
@if (label) {
|
@if (label) {
|
||||||
<span class="sql-label">{{ label }}</span>
|
<span class="sql-label">{{ label }}</span>
|
||||||
}
|
}
|
||||||
<nav class="sql-nav" [attr.aria-label]="label || 'Quick links'">
|
@if (layout === 'aside') {
|
||||||
@for (link of links; track link.label; let last = $last) {
|
<nav class="sql-aside" [attr.aria-label]="label || 'Quick links'">
|
||||||
<a
|
@for (link of links; track link.label) {
|
||||||
class="sql-link"
|
<a class="sql-aside-link" [routerLink]="link.route" [title]="link.hint || link.label">
|
||||||
[routerLink]="link.route"
|
@if (link.icon) {
|
||||||
[title]="link.hint || link.label"
|
<svg class="sql-aside-icon" viewBox="0 0 24 24" fill="none"
|
||||||
>
|
stroke="currentColor" stroke-width="2"
|
||||||
@if (link.icon) {
|
stroke-linecap="round" stroke-linejoin="round">
|
||||||
<svg class="sql-icon" viewBox="0 0 24 24" fill="none"
|
<path [attr.d]="link.icon"/>
|
||||||
stroke="currentColor" stroke-width="2"
|
</svg>
|
||||||
|
}
|
||||||
|
<div class="sql-aside-content">
|
||||||
|
<span class="sql-aside-title">{{ link.label }}</span>
|
||||||
|
@if (link.description) {
|
||||||
|
<span class="sql-aside-desc">{{ link.description }}</span>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<svg class="sql-arrow" viewBox="0 0 24 24" fill="none"
|
||||||
|
stroke="currentColor" stroke-width="2.5"
|
||||||
stroke-linecap="round" stroke-linejoin="round">
|
stroke-linecap="round" stroke-linejoin="round">
|
||||||
<path [attr.d]="link.icon"/>
|
<path d="M9 6l6 6-6 6"/>
|
||||||
</svg>
|
</svg>
|
||||||
}
|
</a>
|
||||||
<span class="sql-text">{{ link.label }}</span>
|
|
||||||
<svg class="sql-arrow" viewBox="0 0 24 24" fill="none"
|
|
||||||
stroke="currentColor" stroke-width="2.5"
|
|
||||||
stroke-linecap="round" stroke-linejoin="round">
|
|
||||||
<path d="M9 6l6 6-6 6"/>
|
|
||||||
</svg>
|
|
||||||
</a>
|
|
||||||
@if (!last) {
|
|
||||||
<span class="sql-sep" aria-hidden="true"></span>
|
|
||||||
}
|
}
|
||||||
}
|
</nav>
|
||||||
</nav>
|
} @else {
|
||||||
|
<nav class="sql-nav" [attr.aria-label]="label || 'Quick links'">
|
||||||
|
@for (link of links; track link.label; let last = $last) {
|
||||||
|
<a
|
||||||
|
class="sql-link"
|
||||||
|
[routerLink]="link.route"
|
||||||
|
[title]="link.hint || link.label"
|
||||||
|
>
|
||||||
|
@if (link.icon) {
|
||||||
|
<svg class="sql-icon" viewBox="0 0 24 24" fill="none"
|
||||||
|
stroke="currentColor" stroke-width="2"
|
||||||
|
stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path [attr.d]="link.icon"/>
|
||||||
|
</svg>
|
||||||
|
}
|
||||||
|
<span class="sql-text">{{ link.label }}</span>
|
||||||
|
<svg class="sql-arrow" viewBox="0 0 24 24" fill="none"
|
||||||
|
stroke="currentColor" stroke-width="2.5"
|
||||||
|
stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path d="M9 6l6 6-6 6"/>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
@if (!last) {
|
||||||
|
<span class="sql-sep" aria-hidden="true"></span>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</nav>
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
styles: [`
|
styles: [`
|
||||||
:host {
|
:host {
|
||||||
@@ -152,6 +181,84 @@ export interface StellaQuickLink {
|
|||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Aside layout — vertical list with descriptions */
|
||||||
|
:host([layout=aside]) {
|
||||||
|
border-top: none;
|
||||||
|
padding-top: 0;
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sql-aside {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sql-aside-link {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 0.5rem;
|
||||||
|
padding: 0.625rem 0.75rem;
|
||||||
|
text-decoration: none;
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
border-left: 2px solid transparent;
|
||||||
|
transition: background 120ms ease, border-color 120ms ease, color 120ms ease;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sql-aside-link:hover {
|
||||||
|
background: var(--color-surface-secondary);
|
||||||
|
border-left-color: var(--color-brand-primary);
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sql-aside-link:focus-visible {
|
||||||
|
outline: 2px solid var(--color-focus-ring);
|
||||||
|
outline-offset: -2px;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sql-aside-icon {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-top: 1px;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sql-aside-link:hover .sql-aside-icon {
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sql-aside-content {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sql-aside-title {
|
||||||
|
display: block;
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 1.3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sql-aside-desc {
|
||||||
|
display: block;
|
||||||
|
font-size: 0.6875rem;
|
||||||
|
line-height: 1.4;
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
margin-top: 0.125rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sql-aside-link:hover .sql-aside-desc {
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sql-aside-link .sql-arrow {
|
||||||
|
margin-top: 2px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
`],
|
`],
|
||||||
})
|
})
|
||||||
export class StellaQuickLinksComponent {
|
export class StellaQuickLinksComponent {
|
||||||
@@ -160,4 +267,7 @@ export class StellaQuickLinksComponent {
|
|||||||
|
|
||||||
/** Optional heading label (e.g. "Related", "Shortcuts", "Jump to"). */
|
/** Optional heading label (e.g. "Related", "Shortcuts", "Jump to"). */
|
||||||
@Input() label?: string;
|
@Input() label?: string;
|
||||||
|
|
||||||
|
/** Layout variant: 'inline' (horizontal dots) or 'aside' (vertical with descriptions). */
|
||||||
|
@Input() layout: 'inline' | 'aside' = 'inline';
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user