Do NOT attempt to run any command which requires open network communication. Your Dev environment is isolated for safety.
Team Retro follows a modern Angular architecture with Firebase integration, focusing on real-time collaboration. Based on research and best practices, this document outlines architectural decisions and patterns without being overly prescriptive on implementation details.
Organize the application by feature areas rather than technical concerns:
src/
├── app/
│ ├── core/ # Core services, guards, interceptors
│ ├── shared/ # Shared components, directives, pipes
│ ├── features/ # Feature modules (by domain)
│ │ ├── auth/ # Authentication components and services
│ │ ├── boards/ # Board management
│ │ ├── sessions/ # Session management and phases
│ │ └── ... # Other feature areas
└── styles/ # Global styles and theme
- Component-Based: Build using a component-based architecture
- Standalone Components: Use Angular's standalone component pattern
- OnPush Change Detection: Apply throughout for performance
- Single Responsibility: Keep components and services focused
- Composition Over Inheritance: Use composition patterns for reuse
Angular 19's Signals should be the primary state management pattern:
- Component-Level State: Use signals for local component state
- Service-Level State: Use signals and computed values in services
- Derived State: Use computed signals for derived values
- Firebase Integration: Convert Firestore Observables to signals
- Centralize Related State: Group related state in dedicated services
- Single Source of Truth: Avoid state duplication
- Unidirectional Data Flow: Follow clear state update patterns
- Immutable Updates: Make state updates using immutable patterns
- Local vs. Shared State: Balance component vs. service-level state
Based on research into Firebase best practices for real-time collaboration:
- Document-Based Structure: Design around Firestore's document-oriented model
- Collection Hierarchy: Consider parent-child relationships in collection design
- Document Size: Keep documents small and focused (under 1MB)
- Denormalization Strategy: Balance normalization with query efficiency
- Avoid Hotspots: Distribute writes to prevent contention
- Observable Pattern: Leverage Firestore's observable-based APIs
- Reactive Data Binding: Connect Firebase data to Angular's reactivity model
- Optimistic Updates: Update UI before Firebase confirms for responsiveness
- Conflict Resolution: Implement strategies for concurrent edits
- Offline Support: Consider offline capability and synchronization
- Rules-Based Security: Implement Firestore security rules for access control
- Role-Based Access: Map application roles to security rules
- Data Validation: Enforce data integrity through rules
- Phase-Based Permissions: Adjust permissions based on current phase
- Presentational & Container Split: Separate data management from presentation
- Input/Output Pattern: Clear component interfaces with @Input() and @Output()
- Content Projection: Use for flexible, composable components
- Component Communication: Follow established Angular patterns
- Control Flow Syntax: Use Angular 19's @if, @for, @switch syntax
- Required Inputs: Leverage required inputs for clearer component contracts
- Functional Route Guards: Use the new functional guard pattern
- Signals API: Fully adopt the Signals API for reactivity
- Dependency Injection: Use the inject() function for DI
Research indicates these approaches work well for real-time collaboration:
- Live Data Synchronization: Use Firestore's real-time listeners
- Presence Indicators: Track active users and their status
- Concurrent Edits: Design for handling multiple simultaneous changes
- Transaction-Based Operations: Use Firestore transactions for critical operations
- Batched Writes: Group related writes with batch operations
- Listener Management: Carefully manage the number of active listeners
- Query Optimization: Design queries for efficiency with proper indexes
- Throttling & Debouncing: Apply for high-frequency events
- Pagination: Implement for large data sets
- Lazy Loading: Apply for features and assets
Use Jasmine for unit testing and Karma for end-to-end testing.
You are offline; if the user needs to install more packages for you or mock stuff for you, let them know (esp. if you find yourself stubbbing a bunch)
npm test runs ng test --no-watch --no-progress --browsers=ChromeHeadless
- Unit Testing: Focus on services and complex components
- Component Testing: Test component behavior in isolation
- Integration Testing: Test feature workflows
- Firebase Emulator: Leverage for testing Firestore integration
- E2E Testing: Cover critical user journeys
- CI/CD Pipeline: Implement for automated testing and deployment
- Environment Configuration: Separate dev, test, and production
- Monitoring & Logging: Implement for production visibility
- Firebase Hosting: Deploy seamlessly to Firebase infrastructure
- Performance Monitoring: Track application performance metrics
For key architectural decisions, document:
- Context: What is driving the decision
- Options: What alternatives were considered
- Decision: What approach was chosen and why
- Consequences: What trade-offs result from this decision
This ensures the team understands major architectural choices and their rationale.
- Regular Refactoring: Schedule time for technical debt reduction
- Upgrade Path: Plan for Angular and Firebase version upgrades
- New Patterns: Adopt emerging patterns as they mature
- Performance Optimization: Continuously monitor and improve
- Scalability Testing: Regularly test with increased load
Please use TSDoc format for documenting internal TypeScript code. These guidelines aim to produce minimal, high-value documentation for our Angular codebase that focuses on WHY code exists rather than WHAT it does.
A TSDoc comment block starts with /** and ends with */. Each line within the block should begin with a *.
/**
* Brief description focusing on purpose or business context.
*/For internal projects, avoid adding comments when:
- The code is self-explanatory
- The functionality is obvious from the code itself
- You would just be explaining how Angular/TypeScript works
- The comment would only restate variable or function names
- The method is a simple getter/setter or straightforward implementation
- Add TSDoc comments only to APIs or code sections that have non-obvious business logic or implementation constraints
- Module-level documentation is rarely needed unless the file has a specific cross-cutting concern
- For function overloads, only document when the differences between overloads involve important business logic
- Purpose over implementation: Focus on WHY code exists, not HOW it works
- Business context: Explain business rules, security constraints, or performance considerations
- Architectural context: Document relationships between components only when non-obvious
- Extreme brevity: Keep comments to 1-2 sentences whenever possible
For internal projects, most tags are unnecessary. Use only these tags when they add genuine value:
-
@param <name> - <description>: Only for parameters with non-obvious constraints or business rules- Always use a hyphen after the parameter name
-
@returns <description>: Only when the return value has important constraints or edge cases -
@throws: Document exceptional conditions that affect system behavior -
@deprecated: When a feature is planned for removal, with migration path
For internal projects, you can usually omit these tags in favor of simple text:
@remarks: Just add another paragraph of text instead@example: Only include for truly complex APIs with non-obvious usage patterns@typeParam: Only needed for complex generic constraints
Use sparingly, and only when they add genuine value:
{@link identifier}: Reference other components when their relationship is non-obvious
- Be minimal: Less documentation is better than obvious documentation
- Be concise: 1-2 sentences is usually sufficient
- Focus on WHY: Explain business context or architectural decisions
- Skip the obvious: Never document what can be inferred from code
/**
* Transforms board data to maintain backward compatibility with legacy format.
* Boards created before v2.0 stored votes differently, and this normalization
* is required to support existing users during the migration period.
*/
function transformBoardData(board: Board): NormalizedBoard {
// Implementation
}/**
* Gets the current user.
*
* @returns The current user
*/
function getCurrentUser(): User {
return this.user;
}/**
* Redirects unauthorized users to login. This enforces the business requirement
* that all board interactions must be authenticated for audit purposes.
*/
const redirectUnauthorizedToLogin = () => redirectUnauthorizedTo(["login"]);-
Document WHY, not WHAT: Explain business context, not implementation details
-
Be extremely selective: Most code should be self-explanatory; only document the exceptions
-
Focus on maintenance concerns: What would a new team member need to know?
-
Prioritize clarity in code: Well-named variables and functions reduce the need for comments
-
Document by exception: 80% of your code should need no documentation
-
Use trackBy with @for loops
-
Lazy load feature modules using loadComponent
-
Use pure pipes instead of methods in templates
-
Optimize images and assets
-
Implement virtual scrolling for large lists
-
Keep Firebase reads minimal with proper queries and caching
-
Use standalone components
-
Use new zoneless
-
Avoid nested subscriptions - use appropriate RxJS operators
-
Use async pipes in templates when possible
-
Consider Firebase Security Rules alongside application code
-
Leverage Firebase indexes for complex queries
-
Use AngularFire exclusively (never mix with raw Firebase SDK)
-
Structure Firestore queries for efficiency: const q = query( collection(this.firestore, 'items'), where('status', '==', 'active'), orderBy('createdAt', 'desc'), limit(10) );
-
Use batch operations for related writes: const batch = writeBatch(this.firestore); updates.forEach(u => batch.update(doc(this.firestore, 'items', u.id), u.data)); await batch.commit();
-
Use transactions for atomic operations: await runTransaction(this.firestore, async tx => { const docA = await tx.get(docRefA); const docB = await tx.get(docRefB); // Ensure atomic updates between documents tx.update(docRefA, { count: docA.data().count - 1 }); tx.update(docRefB, { count: docB.data().count + 1 }); });
-
Use OnPush change detection for all components
-
Use signals for component-internal state: readonly items = signal<Item[]>([]); readonly filteredItems = computed(() => this.items().filter(i => i.isActive));
-
Use observables only for external state (services, HTTP)
-
Use discriminated unions for complex state: type RequestState = | { status: 'idle' } | { status: 'loading' } | { status: 'success', data: T } | { status: 'error', error: Error };
-
Be specific with types, not verbose: // GOOD interface User { id: string; name: string; roles: string[] } const users: User[] = [];
// BAD const value: string = 'hello'; // Type inference makes this redundant
-
Use required inputs: @Input({ required: true }) id!: string; @Input() size: 'sm' | 'md' | 'lg' = 'md';
-
Always use new control flow syntax: @if (condition) { ... } @else { ... } @for (item of items(); track item.id) { ... } @empty { ... } @switch (status()) { @case ('value') { ... } @default { ... } }