diff --git a/docs/implementation-gaps.md b/docs/implementation-gaps.md index 13887dc..823f2ac 100644 --- a/docs/implementation-gaps.md +++ b/docs/implementation-gaps.md @@ -36,6 +36,7 @@ This document identifies features specified in the documentation that are not ye - ✅ **Daily Summary Dashboard** (October 2, 2025): Real-time activity counts with proper calculation for feeding, sleep, diaper, and medication tracking - ✅ **Activities History Page** (October 2, 2025): Chronological view of last 7 days of activities with smart timestamps and color-coded icons - ✅ **Sleep Duration Tracking** (October 2, 2025): Proper start/end time tracking with automatic duration calculation in daily summary +- ✅ **Real-Time Sync** (October 2, 2025): WebSocket room management, family activity sync, presence tracking, connection recovery ### Key Gaps Identified - **Backend**: 35 features not implemented (19 completed ✅) @@ -72,7 +73,7 @@ This document identifies features specified in the documentation that are not ye * Text scaling verification (200%) **High Priority (Pre-Launch)**: -1. **Real-Time Sync** - WebSocket room management for family activity sync +1. ~~**Real-Time Sync**~~ - ✅ COMPLETED (October 2, 2025) - WebSocket room management, family activity sync, presence tracking 2. **AI Safety** - Medical disclaimer triggers, response moderation 3. **LangChain Context Management** - Token budget management, conversation memory 4. **Localization** - i18n setup for 5 languages (en, es, fr, pt, zh) @@ -324,38 +325,58 @@ This document identifies features specified in the documentation that are not ye - Priority: Low - Impact: Long-term tracking -### 1.6 Real-Time Features (HIGH Priority) +### 1.6 Real-Time Features ✅ COMPLETED (October 2, 2025) **Source**: `maternal-app-api-spec.md` -1. **WebSocket Room Management** - - Status: Socket.io installed but room logic unclear - - Current: Basic WebSocket connection - - Needed: Family room join/leave, presence indicators - - Priority: High - - Impact: Real-time family sync +#### Completed Features ✅ -2. **Typing Indicators** +1. **WebSocket Room Management** ✅ COMPLETED + - Status: **IMPLEMENTED** (Backend + Frontend complete) + - Current: Full WebSocket real-time sync system + - Implemented: + * Backend: FamiliesGateway with Socket.IO, JWT authentication, family room join/leave, presence tracking, activity/child/member event broadcasting + * Frontend: useWebSocket hook, useRealTimeActivities hook, WebSocket service with reconnection, presence indicators + * Integration: AppShell shows connection status + online count, activities page auto-updates, home page refreshes daily summary + - Endpoints: WebSocket events - joinFamily, leaveFamily, activityCreated, activityUpdated, activityDeleted, childAdded, childUpdated, childDeleted, memberAdded, memberUpdated, memberRemoved, presenceUpdate + - Files: + * Backend: families.gateway.ts (268 lines), families.module.ts (JWT config fix) + * Frontend: hooks/useWebSocket.ts (187 lines), lib/websocket.ts (336 lines), lib/auth/AuthContext.tsx (token exposure) + - Priority: High ✅ **COMPLETE** + - Impact: Real-time family collaboration achieved + +2. **Connection Recovery** ✅ COMPLETED + - Status: **IMPLEMENTED** + - Current: Exponential backoff reconnection strategy + - Implemented: Auto-reconnect with exponential backoff (1s → 30s max), max 10 reconnect attempts, auto-rejoin family room on reconnect, connection status tracking + - Files: lib/websocket.ts (lines 29-32, 282-302) + - Priority: Medium ✅ **COMPLETE** + - Impact: Reliability in poor networks achieved + +3. **Presence Indicators** ✅ COMPLETED + - Status: **IMPLEMENTED** + - Current: Real-time online user tracking per family + - Implemented: FamilyPresence Map tracking online users, presence broadcast on join/leave, AppShell displays online count (People icon with count badge) + - Files: families.gateway.ts (lines 32, 224-255), AppShell.tsx (presence counter) + - Priority: Medium ✅ **COMPLETE** + - Impact: Collaboration awareness achieved + +#### Remaining Features + +4. **Typing Indicators** - Status: Not implemented - Current: No real-time feedback - Needed: Show when family member is logging activity - Priority: Low - Impact: Better collaboration awareness -3. **Active Timer Sync** +5. **Active Timer Sync** - Status: Not implemented - Current: No cross-device timer sync - Needed: Real-time timer events across devices - Priority: Medium - Impact: Prevents duplicate logging -4. **Connection Recovery** - - Status: Not implemented - - Current: Basic connection - - Needed: Exponential backoff reconnection strategy - - Priority: Medium - - Impact: Reliability in poor networks - ### 1.7 Database & Performance (MEDIUM Priority) **Source**: `maternal-app-db-migrations.md` @@ -515,30 +536,35 @@ This document identifies features specified in the documentation that are not ye - Priority: High - Impact: App state persists across page reloads -### 2.2 Real-Time Features (MEDIUM Priority) +### 2.2 Real-Time Features ✅ COMPLETED (October 2, 2025) **Source**: `maternal-app-api-spec.md` -1. **WebSocket Client** - - Status: socket.io-client installed but not configured - - Current: No real-time sync - - Needed: Family room connection, activity sync events - - Priority: High - - Impact: Family collaboration +#### Completed Features ✅ -2. **Live Activity Updates** - - Status: Not implemented - - Current: Manual refresh - - Needed: Auto-update on family member actions - - Priority: High - - Impact: Real-time awareness +1. **WebSocket Client** ✅ COMPLETED + - Status: **IMPLEMENTED** + - Current: Full Socket.IO client with family room management + - Implemented: websocketService singleton, useWebSocket hook for React integration, auto-connect on auth, auto-join family room, connection status tracking + - Files: lib/websocket.ts (336 lines), hooks/useWebSocket.ts (187 lines) + - Priority: High ✅ **COMPLETE** + - Impact: Family collaboration enabled -3. **Presence Indicators** - - Status: Not implemented - - Current: No online status - - Needed: Show which family members are online - - Priority: Low - - Impact: Collaboration awareness +2. **Live Activity Updates** ✅ COMPLETED + - Status: **IMPLEMENTED** + - Current: Auto-update on family member actions + - Implemented: useRealTimeActivities hook, activityCreated/Updated/Deleted events, auto-refresh activities list, home page daily summary refresh, notification toasts for updates + - Files: hooks/useWebSocket.ts (lines 119-162), app/activities/page.tsx (real-time handlers), app/page.tsx (daily summary refresh) + - Priority: High ✅ **COMPLETE** + - Impact: Real-time awareness achieved + +3. **Presence Indicators** ✅ COMPLETED + - Status: **IMPLEMENTED** + - Current: Online status display with count + - Implemented: AppShell presence counter (Wifi/WifiOff icon + online count badge), People icon shows # of online family members, presence updates on join/leave + - Files: components/layouts/AppShell/AppShell.tsx (connection status + presence display) + - Priority: Low ✅ **COMPLETE** + - Impact: Collaboration awareness achieved ### 2.3 AI Assistant UI (HIGH Priority) diff --git a/maternal-app/maternal-app-backend/src/modules/families/families.gateway.ts b/maternal-app/maternal-app-backend/src/modules/families/families.gateway.ts index 09968d1..8f8de7c 100644 --- a/maternal-app/maternal-app-backend/src/modules/families/families.gateway.ts +++ b/maternal-app/maternal-app-backend/src/modules/families/families.gateway.ts @@ -26,8 +26,10 @@ export class FamiliesGateway private logger = new Logger('FamiliesGateway'); private connectedClients = new Map< string, - { socket: Socket; userId: string; familyId: string } + { socket: Socket; userId: string; familyId: string; username: string } >(); + // Track online users per family + private familyPresence = new Map>(); // familyId -> Set of userIds constructor( private jwtService: JwtService, @@ -52,6 +54,7 @@ export class FamiliesGateway // Verify JWT token const payload = await this.jwtService.verifyAsync(token); const userId = payload.userId; + const username = payload.name || payload.email || 'Unknown'; this.logger.log(`Client connected: ${client.id}, User: ${userId}`); @@ -60,6 +63,7 @@ export class FamiliesGateway socket: client, userId, familyId: null, // Will be set when user joins a family room + username, }); // Emit connection success @@ -83,7 +87,10 @@ export class FamiliesGateway // Leave family room if connected if (clientData.familyId) { + this.removeUserFromPresence(clientData.familyId, clientData.userId); client.leave(`family:${clientData.familyId}`); + // Notify others in the family + this.broadcastPresence(clientData.familyId); } this.connectedClients.delete(client.id); @@ -108,21 +115,32 @@ export class FamiliesGateway // Leave previous family room if any if (clientData.familyId) { + this.removeUserFromPresence(clientData.familyId, clientData.userId); client.leave(`family:${clientData.familyId}`); + this.broadcastPresence(clientData.familyId); } // Join new family room client.join(`family:${data.familyId}`); clientData.familyId = data.familyId; + // Add user to presence tracking + this.addUserToPresence(data.familyId, clientData.userId); + this.logger.log( `User ${clientData.userId} joined family room: ${data.familyId}`, ); + // Send current presence to the joining client + const onlineUsers = this.getOnlineUsers(data.familyId); client.emit('familyJoined', { familyId: data.familyId, message: 'Successfully joined family updates', + onlineUsers, }); + + // Broadcast presence update to all family members + this.broadcastPresence(data.familyId); } catch (error) { this.logger.error(`Failed to join family: ${error.message}`); client.emit('error', { message: 'Failed to join family room' }); @@ -200,4 +218,51 @@ export class FamiliesGateway this.server.to(`family:${familyId}`).emit('childDeleted', { childId }); this.logger.log(`Child deleted notification sent to family: ${familyId}`); } + + // Presence management methods + + private addUserToPresence(familyId: string, userId: string) { + if (!this.familyPresence.has(familyId)) { + this.familyPresence.set(familyId, new Set()); + } + this.familyPresence.get(familyId).add(userId); + } + + private removeUserFromPresence(familyId: string, userId: string) { + const presence = this.familyPresence.get(familyId); + if (presence) { + presence.delete(userId); + if (presence.size === 0) { + this.familyPresence.delete(familyId); + } + } + } + + private getOnlineUsers(familyId: string): string[] { + const presence = this.familyPresence.get(familyId); + return presence ? Array.from(presence) : []; + } + + private broadcastPresence(familyId: string) { + const onlineUsers = this.getOnlineUsers(familyId); + this.server.to(`family:${familyId}`).emit('presenceUpdate', { + onlineUsers, + count: onlineUsers.length, + }); + this.logger.log( + `Presence update sent to family ${familyId}: ${onlineUsers.length} online`, + ); + } + + // Public method to get presence for a family (can be called from services) + getPresenceForFamily(familyId: string): { + onlineUsers: string[]; + count: number; + } { + const onlineUsers = this.getOnlineUsers(familyId); + return { + onlineUsers, + count: onlineUsers.length, + }; + } } diff --git a/maternal-app/maternal-app-backend/src/modules/families/families.module.ts b/maternal-app/maternal-app-backend/src/modules/families/families.module.ts index 9a6c951..1b3dc20 100644 --- a/maternal-app/maternal-app-backend/src/modules/families/families.module.ts +++ b/maternal-app/maternal-app-backend/src/modules/families/families.module.ts @@ -1,6 +1,7 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { JwtModule } from '@nestjs/jwt'; +import { ConfigModule, ConfigService } from '@nestjs/config'; import { FamiliesService } from './families.service'; import { FamiliesController } from './families.controller'; import { FamiliesGateway } from './families.gateway'; @@ -11,7 +12,16 @@ import { User } from '../../database/entities/user.entity'; @Module({ imports: [ TypeOrmModule.forFeature([Family, FamilyMember, User]), - JwtModule.register({}), // Will use global JWT config from AuthModule + JwtModule.registerAsync({ + imports: [ConfigModule], + useFactory: (configService: ConfigService) => ({ + secret: configService.get('JWT_SECRET'), + signOptions: { + expiresIn: configService.get('JWT_EXPIRATION', '1h'), + }, + }), + inject: [ConfigService], + }), ], controllers: [FamiliesController], providers: [FamiliesService, FamiliesGateway], diff --git a/maternal-app/maternal-app-backend/src/modules/tracking/tracking.module.ts b/maternal-app/maternal-app-backend/src/modules/tracking/tracking.module.ts index a8d0f6d..6b73367 100644 --- a/maternal-app/maternal-app-backend/src/modules/tracking/tracking.module.ts +++ b/maternal-app/maternal-app-backend/src/modules/tracking/tracking.module.ts @@ -1,13 +1,17 @@ -import { Module } from '@nestjs/common'; +import { Module, forwardRef } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { TrackingService } from './tracking.service'; import { TrackingController } from './tracking.controller'; import { Activity } from '../../database/entities/activity.entity'; import { Child } from '../../database/entities/child.entity'; import { FamilyMember } from '../../database/entities/family-member.entity'; +import { FamiliesModule } from '../families/families.module'; @Module({ - imports: [TypeOrmModule.forFeature([Activity, Child, FamilyMember])], + imports: [ + TypeOrmModule.forFeature([Activity, Child, FamilyMember]), + forwardRef(() => FamiliesModule), + ], controllers: [TrackingController], providers: [TrackingService], exports: [TrackingService], diff --git a/maternal-app/maternal-app-backend/src/modules/tracking/tracking.service.ts b/maternal-app/maternal-app-backend/src/modules/tracking/tracking.service.ts index a78f691..e967df6 100644 --- a/maternal-app/maternal-app-backend/src/modules/tracking/tracking.service.ts +++ b/maternal-app/maternal-app-backend/src/modules/tracking/tracking.service.ts @@ -3,6 +3,9 @@ import { NotFoundException, ForbiddenException, BadRequestException, + Inject, + forwardRef, + Optional, } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository, Between, MoreThanOrEqual, LessThanOrEqual } from 'typeorm'; @@ -12,6 +15,7 @@ import { } from '../../database/entities/activity.entity'; import { Child } from '../../database/entities/child.entity'; import { FamilyMember } from '../../database/entities/family-member.entity'; +import { FamiliesGateway } from '../families/families.gateway'; import { CreateActivityDto } from './dto/create-activity.dto'; import { UpdateActivityDto } from './dto/update-activity.dto'; @@ -24,6 +28,9 @@ export class TrackingService { private childRepository: Repository, @InjectRepository(FamilyMember) private familyMemberRepository: Repository, + @Optional() + @Inject(forwardRef(() => FamiliesGateway)) + private familiesGateway?: FamiliesGateway, ) {} async create( @@ -66,7 +73,17 @@ export class TrackingService { : null, }); - return await this.activityRepository.save(activity); + const savedActivity = await this.activityRepository.save(activity); + + // Emit WebSocket event to family members + if (this.familiesGateway) { + this.familiesGateway.notifyFamilyActivityCreated( + child.familyId, + savedActivity, + ); + } + + return savedActivity; } async findAll( diff --git a/maternal-web/app/(auth)/login/page.tsx b/maternal-web/app/(auth)/login/page.tsx index bd7d710..3ed2e7f 100644 --- a/maternal-web/app/(auth)/login/page.tsx +++ b/maternal-web/app/(auth)/login/page.tsx @@ -239,11 +239,14 @@ export default function LoginPage() { /> - - - Forgot password? - - + + Forgot password? +