Component Strategy
This document defines how Synapse UI components are structured, styled, tested, and published.
Standalone-First
Every component is an Angular standalone component. We do not ship or use NgModule wrappers.
@Component({
selector: 'synapse-ai-prompt-bar',
standalone: true,
imports: [CommonModule],
templateUrl: './ai-prompt-bar.component.html',
styleUrl: './ai-prompt-bar.component.css',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AiPromptBarComponent { }
Rationale: Standalone is the default in modern Angular (v19+). It simplifies consumer imports and aligns with tree-shaking goals. See ADR 002.
Change Detection
All components use ChangeDetectionStrategy.OnPush.
For streaming components (AiStreamContainer), this is non-negotiable — default change detection would re-render the entire subtree on every SSE chunk, causing jank.
Signal-Based Inputs (Angular 21+)
Prefer signal inputs and computed values for reactive state:
readonly chunks = input<string[]>([]);
readonly renderedHtml = computed(() => this.markdownService.render(this.chunks()));
Mark dirty explicitly only when integrating with non-signal APIs (e.g., EventSource callbacks) via ChangeDetectorRef.markForCheck() or by updating signals.
Headless Foundation (Angular CDK)
Use CDK primitives for complex behavior:
| Need | CDK Module |
|---|---|
| Overlays, tooltips | @angular/cdk/overlay |
| Focus trapping in modals | @angular/cdk/a11y |
| Drag-and-drop reorder | @angular/cdk/drag-drop |
| Virtual scrolling (long streams) | @angular/cdk/scrolling |
Components expose opinionated UI on top of these primitives — not raw CDK re-exports.
Styling Rules
- Token-only colors — Use
var(--synapse-*)custom properties. Never hardcode#hexin component CSS. - Component-scoped styles —
:hostand:host-contextfor theme variants if needed. - No global CSS leakage — Global styles live only in
@synapse-ui/theme. - Three themes minimum — Light, Dark, High-Contrast must be visually verified in Storybook.
Component File Structure
libs/ui/ai-prompt/
├── src/
│ ├── index.ts # Public API barrel
│ └── lib/
│ ├── ai-prompt-bar.component.ts
│ ├── ai-prompt-bar.component.html
│ ├── ai-prompt-bar.component.css
│ ├── ai-prompt-bar.component.spec.ts
│ └── ai-prompt-bar.stories.ts
├── project.json
├── ng-package.json
├── README.md # Package-level readme (npm)
└── package.json # Package metadata
Public API Discipline
- Export only what consumers need from
index.ts. - Prefix selectors with
synapse-to avoid collisions. - Use
output()for events; avoid@Output()decorator in new code. - Document breaking changes in CHANGELOG and ADRs.
Storybook Requirements
Every component ships with stories covering:
| Story | Purpose |
|---|---|
| Default | Happy path |
| Disabled / Read-only | State variants |
| With theme switcher | Light, Dark, High-Contrast |
| Interactive | User actions (submit, stop, copy) |
| Edge cases | Empty input, very long content, rapid streaming |
Accessibility Checklist
- Keyboard navigable (Tab, Enter, Escape)
- Visible focus indicators using
--synapse-focus-ring - ARIA labels on icon-only buttons
- Live regions for streaming content updates (
aria-live="polite") - Sufficient color contrast in all themes
Testing Pyramid
| Level | Tool | Scope |
|---|---|---|
| Unit | Jest | Services, parsers, component logic |
| Component | Testing Library + Jest | DOM interactions, a11y |
| Visual | Storybook | Manual + optional Chromatic |
| E2E | Cypress | Sandbox integration flows |