---
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.
