diff --git a/src/Web/StellaOps.Web/AGENTS.md b/src/Web/StellaOps.Web/AGENTS.md index 8014ad144..4bb782162 100644 --- a/src/Web/StellaOps.Web/AGENTS.md +++ b/src/Web/StellaOps.Web/AGENTS.md @@ -112,6 +112,61 @@ Do NOT create custom `.stat-card`, `.summary-card`, `.kpi-card`, or `.posture-ca - If `route` is set: card is clickable with hover lift + arrow - If no `route`: static display, no hover effect +## Horizontal Scroll Card Lane Pattern (Reusable) + +A reusable pattern for displaying actionable items as horizontally scrollable cards with +gradient fades and scroll arrows. Used in the dashboard (environment cards, pending actions) +and the approvals inbox (approval cards with inline actions). + +### Architecture + +Three layers: +1. **Wrapper** (`*-lane-wrapper`) — relative-positioned container with `::before`/`::after` gradient pseudo-elements +2. **Scroll container** (`*-lane`) — flex row with `overflow-x: auto`, hidden scrollbar, `scroll-behavior: smooth` +3. **Cards** — fixed-width (`280px`) flex items with `flex-shrink: 0` + +### Scroll arrow signals (TypeScript) + +```typescript +@ViewChild('myScroll') myScrollRef?: ElementRef; +readonly showLeftArrow = signal(false); +readonly showRightArrow = signal(false); + +onScroll(): void { this.updateArrows(); } +scrollCards(direction: 'left' | 'right'): void { + this.myScrollRef?.nativeElement?.scrollBy({ left: direction === 'left' ? -300 : 300, behavior: 'smooth' }); +} +private updateArrows(): void { + const el = this.myScrollRef?.nativeElement; + if (!el) { this.showLeftArrow.set(false); this.showRightArrow.set(false); return; } + this.showLeftArrow.set(el.scrollLeft > 1); + this.showRightArrow.set(el.scrollWidth - el.scrollLeft - el.clientWidth > 1); +} +``` + +### Gradient fades (CSS) + +```scss +.my-wrapper.can-scroll-left::before { + content: ''; position: absolute; top: 0; left: 0; bottom: 0; width: 56px; + background: linear-gradient(to right, var(--color-surface-primary) 0%, transparent 100%); + pointer-events: none; z-index: 1; +} +.my-wrapper.can-scroll-right::after { /* mirror for right side */ } +``` + +### Confirmation dialogs for card actions + +- **Production approve**: `` with `variant="warning"` and `ViewChild` ref, call `.open()` programmatically +- **Reject with reason**: Custom inline dialog overlay with `