Expire live presence from heartbeats
Derive live presence from heartbeat deadlines and schedule the next exact expiration without polling continuously.Description
Requirements
Export PresenceRoster from src/App.tsx.
Show a user as present only while seenAt plus ttlMs is greater than clock.now().
Extend a user's deadline when a newer heartbeat for the same user arrives.
Schedule one callback for the nearest live expiration and recompute after it fires.
Render one roster entry per user using that user's newest valid heartbeat.
Expose visible status and interactive controls with native semantics, accessible names, and keyboard operation appropriate to the workflow.
Do not mutate props, nested caller-owned values, or arrays supplied to the component.
Natural expiry
Ada was seen at 1000 with ttl 5000
Ada disappears exactly when the clock reaches 6000
Show a user as present only while seenAt plus ttlMs is greater than clock.now().
Renewed heartbeat
A newer heartbeat arrives before expiry
The old timer cannot remove the renewed user early
Extend a user's deadline when a newer heartbeat for the same user arrives.
Constraints
Use React state and effects only where the observable component contract requires themUse injected adapters instead of accessing network, storage, timers, or browser globals directlyKeep one authoritative state transition for each user or adapter event
Hints
Hint 1
Start with the expires-at-deadline behavior and make its observable result deterministic.
Hint 2
Model heartbeat-refreshes independently before combining it with the remaining states.
Hint 3
Verify the boundary described by deduplicates-users without changing caller-owned input.