--- name: realtime-and-live-workflows description: Use tibi-server SSE channels for live website and admin workflows. Covers channel design, subscription hooks, replay/TTL/buffer behavior, permission boundaries, and when realtime fits a website project. --- # realtime-and-live-workflows ## When to use this skill Use this skill when: - A website or admin feature needs live updates - You want SSE-based notifications, preview refreshes, status feeds, or dashboards - Hooks or jobs should push messages to connected clients - You need to decide whether realtime is actually appropriate for the feature ## Goal The goal is to model realtime as a deliberate workflow, not as a random event stream. On this stack, realtime means: - SSE transport - in-memory per-project channels - server-side send from hooks/jobs - subscription endpoints implemented in hooks ## Source of truth Use these sources when implementing or reviewing realtime behavior: - `tibi-server/docs/07-realtime.md` - `tibi-server/docs/11-jobs.md` - the relevant hook files under `api/hooks/` ## Core architecture tibi-server realtime is based on per-project in-memory pub/sub channels. Important characteristics: - channels are created on demand - channels are isolated per project - the transport is SSE, not WebSockets - messages are not durable across restarts - hooks subscribe, hooks or jobs send This makes realtime useful for live UX, but not for durable messaging. ## Good use cases for website projects Good fits: - live status or progress streams - lightweight admin notifications - system messages pushed from jobs - preview or refresh signals after mutations - dashboards with current in-memory activity Weak fits: - business-critical guaranteed delivery - cross-instance distributed eventing - durable queue semantics - workflows that require replay beyond a bounded in-memory buffer ## Subscription design Realtime subscriptions should be exposed intentionally through dedicated read hooks that hold the SSE connection open. Design the endpoint around: - who may subscribe - which channel names exist - what event shape clients receive - how replay and freshness should work Do not expose a generic raw event hose unless the project truly needs that. ## Channel options that matter When modeling realtime behavior, decide these explicitly: - `bufferSize` - `onFull` - `messageTTL` - `lastN` - `maxAge` These are product decisions, not low-level afterthoughts. ### Buffer size Use a larger buffer only when reconnecting clients should receive some recent history. Do not overestimate it as persistence. ### On-full behavior - `drop-oldest` favors receiving the newest state, even if some history is lost - `drop-newest` preserves older pending messages for the subscriber and skips the new one For most live UI use cases, `drop-oldest` is the more natural choice. ### Replay and freshness Use `lastN` or `maxAge` only when reconnecting clients genuinely benefit from recent context. For notification-like channels, some replay can help. For pure live status indicators, it may be better to show only new events. ## Permission boundaries Channels do not carry independent auth rules. Access is controlled by the hook/collection permission layer that exposes the SSE endpoint. That means: - secure the subscription endpoint, not just the client code - do not assume channel names themselves protect access - be explicit about who may connect and what data is safe to send ## Event design Prefer small, explicit event shapes. Good event payloads usually include: - event `type` - relevant identifier - minimal status or message fields - timestamp when useful Avoid pushing whole documents unless the live client truly needs them. ## Hooks vs. jobs Use hooks to send events when changes happen immediately in response to requests. Use jobs to send events when the trigger is scheduled or background-driven. Typical patterns: - hook sends `content-updated` - job sends `maintenance-warning` - hook sends `import-finished` - job sends `daily-report-ready` ## Operational limits This realtime system is intentionally lightweight. Important limits: - messages are lost on server restart - no cross-server synchronization - no durable backlog - slow subscribers can miss messages due to ring-buffer behavior If the feature cannot tolerate these limits, this realtime system is the wrong abstraction. ## Recommended modeling patterns ### Live admin notifications Recommended shape: - authenticated SSE endpoint - narrow event schema - optional short replay via `lastN` ### Preview refresh signal Recommended shape: - hook emits lightweight invalidation or refresh event - client decides whether to refetch - do not stream full content when a simple signal is enough ### Scheduled status feed Recommended shape: - job emits events to a system channel - UI listens and renders current status - TTL keeps stale messages from resurfacing after reconnect ## Anti-patterns - Using realtime as a replacement for persistence - Publishing sensitive data because “the UI needs it quickly” - Creating one generic catch-all channel for unrelated features - Ignoring replay/TTL/buffer behavior and assuming delivery guarantees ## Verification checklist After adding realtime behavior, verify all of these: 1. The subscription endpoint is permissioned correctly. 2. The event shape is explicit and minimal. 3. Replay/TTL/buffer settings match the intended UX. 4. Disconnect/reconnect behavior is acceptable. 5. The feature still behaves sensibly after a server restart. 6. `yarn validate` stays clean. ## What an LLM should inspect first When asked to add realtime to this starter, inspect in this order: 1. `tibi-server/docs/07-realtime.md` 2. the hook that should expose or emit the events 3. whether a job is also part of the workflow 4. the permission boundary of the SSE endpoint 5. the exact event contract the client needs This prevents building live features with unclear delivery or security assumptions.