---
id: TASK-47
title: Implement System Clock / Heartbeat Service for rSpace canvas
status: Done
assignee: []
created_date: '2026-02-18 22:30'
updated_date: '2026-03-11 23:19'
labels:
  - feature
  - infrastructure
  - ecosystem
milestone: m-1
dependencies:
  - TASK-43
references:
  - >-
    rspace-online/backlog/tasks/task-43 -
    Implement-Event-Broadcasting-canvas-wide-pub-sub-system.md
  - rSpace-website/docs/R-ECOSYSTEM-ARCHITECTURE.md
priority: high
---

## Description

<!-- SECTION:DESCRIPTION:BEGIN -->
Add a server-side System Clock service that broadcasts consistent time signals across the rSpace canvas via the Event Bus (TASK-43). This provides a shared temporal heartbeat that all shapes, modules, and connected r-ecosystem apps can subscribe to.

**Why**: Currently there is no shared sense of time passing in rSpace. Individual shapes have no way to trigger time-based behavior (recurring updates, countdowns, reminders, periodic recalculations). A system clock provides the foundational "pulse" that time-aware features depend on.

### Architecture

New file `server/clock-service.ts`:
- `SystemClock` class running server-side (not per-client)
- Emits events on the CanvasEventBus at configurable intervals:
  - `clock:tick` — every 60 seconds (primary heartbeat)
  - `clock:minute:5` — every 5 minutes
  - `clock:hourly` — every hour
  - `clock:daily` — at midnight UTC (configurable timezone)
- Payload includes: `{ timestamp, isoString, hour, minute, dayOfWeek, tickCount }`
- Single authoritative source — server emits, clients receive via CRDT sync
- Graceful degradation: if server is offline, clients can run a local fallback clock

### Shape Integration

Shapes opt in by subscribing to clock channels:
```typescript
// In a Budget shape
subscriptions: ['clock:hourly']
onEventReceived(channel: 'clock:hourly', payload) {
  this.recalculate();
}

// In a Countdown shape
subscriptions: ['clock:tick']
onEventReceived(channel: 'clock:tick', payload) {
  this.updateRemaining(payload.timestamp);
}
```

### Use Cases

- **Timer/Countdown shapes**: Update display every tick
- **Budget/Finance shapes**: Recalculate on hourly boundaries
- **Calendar shapes**: Highlight "now" marker, trigger reminders
- **Presence decay**: Mark users idle after N missed ticks
- **Backlog surfacing**: External services can subscribe to clock events to trigger task reviews
- **rTime.space foundation**: This clock becomes the temporal primitive that rTime.space builds on
- **Workflow triggers**: "Run this workflow every day at 9am"

### Configuration

Server-level config in community settings:
- `clock.enabled: true` (default true)
- `clock.timezone: 'UTC'` (for daily boundary)
- `clock.tickInterval: 60` (seconds, minimum 10)
- `clock.channels: ['tick', 'minute:5', 'hourly', 'daily']`

### Performance Considerations

- Clock events should NOT flood the CRDT eventLog — use a separate lightweight channel or mark as ephemeral (not persisted in ring buffer)
- Server-side only emission prevents N clients from emitting N duplicate ticks
- Tick events are fire-and-forget, no ack required
<!-- SECTION:DESCRIPTION:END -->

## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 SystemClock emits `clock:tick` every 60s via CanvasEventBus
- [x] #2 Configurable intervals: tick, 5-min, hourly, daily
- [x] #3 Server-authoritative — only one clock source per canvas
- [x] #4 Shapes can subscribe to clock channels and receive time payloads
- [x] #5 Clock events are ephemeral (not persisted in CRDT eventLog ring buffer)
- [x] #6 Fallback local clock when server connection is lost
- [x] #7 Clock can be enabled/disabled per community in settings
<!-- AC:END -->

## Implementation Notes

<!-- SECTION:NOTES:BEGIN -->
Implementation started

Complete. Created server/clock-service.ts with SystemClock class (configurable tick/5min/hourly/daily intervals, min 10s). Wired broadcastClockEvent() in server/index.ts. Updated CommunitySync to handle ephemeral 'clock' WebSocket messages. Updated CanvasEventBus with server clock handling + local fallback (fires when server clock lost for 2.5x interval). Clock events bypass CRDT — WebSocket only.
<!-- SECTION:NOTES:END -->
