Skip to main content

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:

NeedCDK 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

  1. Token-only colors — Use var(--synapse-*) custom properties. Never hardcode #hex in component CSS.
  2. Component-scoped styles:host and :host-context for theme variants if needed.
  3. No global CSS leakage — Global styles live only in @synapse-ui/theme.
  4. 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:

StoryPurpose
DefaultHappy path
Disabled / Read-onlyState variants
With theme switcherLight, Dark, High-Contrast
InteractiveUser actions (submit, stop, copy)
Edge casesEmpty 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

LevelToolScope
UnitJestServices, parsers, component logic
ComponentTesting Library + JestDOM interactions, a11y
VisualStorybookManual + optional Chromatic
E2ECypressSandbox integration flows