State model
liebstoeckel keeps several distinct pieces of state, each with its own owner, transport, and authority. This page is the map.
Standalone vs. live
Section titled “Standalone vs. live”On load the engine reads window.__LIEBSTOECKEL_LIVE__ (a <script> the live server injects):
- absent → standalone. The deck is a plain
.html. Nav syncs between the presenter window and the audience window overBroadcastChannel(same machine only). Plugins render their fallback (no shared state). - present → live. The deck connected to a live server. Nav + plugin state live in a shared Yjs document synced over WebSocket, so they cross devices. Each client has a
role(presenter / viewer) from its URL token.
The same built .html works both ways. See Live presenting.
State inventory
Section titled “State inventory”| State | Owner | Transport | Authority | Persistence |
|---|---|---|---|---|
| Slide index / step / total | Deck / PresenterView controller | live: Yjs deck map · standalone: BroadcastChannel | presenter writes, everyone reads | ephemeral (per session) |
Plugin shared state (e.g. poll votes) | the plugin, via pluginState() | Yjs plugin:<id> map (CRDT), or plugin:<id>:<instance> for a named instance | any participant writes (incl. read-only viewers); presenter-only actions gated in plugin code | ephemeral (lost on server restart) |
Brand (data-brand) | Deck / PresenterView | per-window React state | local | n/a |
| Elapsed timer | PresenterView | per-window React state | local | resets per window |
| Participant id | client | sessionStorage | local | survives reload (1 browser session = 1 participant) |
| Overlays (help / blur / overview / QR / jump buffer) | Deck | per-window React state | local UI | n/a |
The two nav controllers (same shape, different transport)
Section titled “The two nav controllers (same shape, different transport)”Deck and PresenterView both pick a controller and call next() / prev() / setIndex() on it:
- standalone →
useDeckSync(count):BroadcastChannelcarries{ index, step, total, startedAt }. A newly opened window broadcastsrequest; the others reply, so it snaps to the live state. - live →
useLiveDeck(doc, count, canDrive): a Yjsdeckmap holds{ index, step, total }. Viewers (canDrive = false) can’t write, so they follow; the presenter drives.next()/prev()read the freshest doc state (no stale-closure on rapid presses) and apply step/slide logic.
In both, steps reveal before the slide advances; the presenter view shows the step counter, the audience just sees the reveals.
Plugin state authority
Section titled “Plugin state authority”Plugin state is a CRDT, so writes merge and there’s no central lock. We assume non-malicious clients: the server does basic checks only and drops oversized/garbage frames, but does not police which field a client writes. “Presenter-only” semantics (e.g. close-voting) are enforced in the plugin’s own UI/logic by checking role, not by the transport.
Resilience
Section titled “Resilience”- Reconnect:
connectLiveauto-reconnects with capped exponential backoff and re-pushes local state on reopen. A clean network blip recovers without a reload. - Half-open detection: the server sends a keepalive (~25 s) and Bun pings idle sockets; the client runs a watchdog and force-reconnects if no frame arrives within
staleMs. A silently-dead (mobile-sleep / NAT-timeout) socket recovers instead of hanging. - Bad frames: every
Y.applyUpdateboundary (client message, serverHub.recv) is wrapped, and the server caps inbound frame size. One malformed update can’t disrupt the session. - Broadcast isolation: the relay guards each per-peer
send; a failing peer is dropped rather than starving the broadcast to the others. - Late join: the server sends the full Yjs state to any newcomer, so opening a link mid-session lands you on the current slide with current plugin state.
See also
Section titled “See also”- Live presenting: sessions, tokens, roles, the windows, and controller selection.
- Relay: taking a session to the public internet without shipping deck code to the relay.
- Plugins overview and State & sync: authoring shared state, fallback, and theming.
Code-first presentations your agent can author. One file out, no server.
Comments