# Architecture

liebstoeckel is a small Bun monorepo: reusable **engine / theme / components** packages, a **plugin** stack, **delivery** packages (live server + relay + thumbnails), an umbrella **CLI**, and `presentations/*` decks.

## Packages & layers

![liebstoeckel package and layer map](https://docs.liebstoeckel.app/diagrams/architecture.svg)

| Package | Role |
| --- | --- |
| `engine` | brand-agnostic deck runtime: slides, steps, Magic Move, `CodeMagic`, presenter view, nav, the live/plugin layer, the build API |
| `theme` | the token model + brand → CSS-variable pipeline (`defineTheme`, `themeToCss`) |
| `components` | themed MDX element map, `Magic`, the atmospheric background |
| `plugin-sdk` | `definePlugin`, the `schema`/`t` types, `pluginState`, discovery + manifest |
| `plugin-ui` | brand-styled primitives (`Card`, `Button`, `Bar`, `Stack`, `Eyebrow`) |
| `plugin-poll` · `plugin-qa` · `plugin-reactions` | the built-in live plugins |
| `live-server` | LAN session: serve + Yjs `Hub` over WebSocket, presenter/viewer tokens |
| `present-relay` | public session: opaque-sandbox deck serving; never runs deck code |
| `thumbnails` | build-time slide capture (Playwright + `Bun.Image` → WebP) |
| `cli` | the umbrella `liebstoeckel` command |

**Separation of concerns:** `engine` knows nothing about a specific look; `theme` defines looks; `components` consume theme tokens; a `presentation` wires a brand + ordered slides + plugins together.

## The build pipeline

![the deck build pipeline](https://docs.liebstoeckel.app/diagrams/build-pipeline.svg)

`Bun.build({ compile: true, target: "browser" })` runs with the Tailwind and MDX plugins. The MDX plugin highlights fenced code with Shiki at build time; `<CodeMagic>` is tokenized by a Bun macro. The plugin manifest (with base64 server bundles) and slide thumbnails are embedded into the single output `.html`.

## Live state flow

![live sync sequence between presenter, relay/hub, and viewers](https://docs.liebstoeckel.app/diagrams/live-sync.svg)

State lives in one Yjs document per session. The presenter writes the slide index and any plugin state; the `Hub` broadcasts updates; viewers apply them and re-render. A plugin's **server** part (if any) runs only on the presenting machine and writes its effects into the same doc. With a relay, the deck HTML is served from an **opaque origin** and the local machine joins as a privileged runner peer.
**Two transports, one model:** Standalone decks sync across your own windows via `BroadcastChannel`; live decks sync across devices via Yjs-over-WebSocket. The slide/step/plugin model is identical. Only the transport differs.

[The rendering pipeline](https://docs.liebstoeckel.app/concepts/rendering/)