# Vuisio module development context

This file gives an AI coding assistant the context to help build a **module**
for Vuisio, a self-hosted SFU for video conferencing written in Rust. Drop it at
the root of your working copy of the Vuisio public repository, named `CLAUDE.md`.

Everything here is in the open scope (AGPL-3.0-only): the SFU core and the SDK.

## What Vuisio is

- A Selective Forwarding Unit (SFU): it forwards WebRTC media without
  transcoding, on top of the `str0m` library.
- The core (`core/server`) is standalone and knows nothing about modules.
- The orchestrator (`bin/vuisio`) starts the SFU and wires its hooks to modules.
- A module is an independent process that connects over gRPC and declares the
  capabilities it provides.

## The dependency rule (do not break it)

`core/server` NEVER depends on `sdk/` or `modules/`. The SFU is standalone. All
module-specific wiring lives in the orchestrator (`bin/vuisio`).

## Where to look

- `sdk/traits/` — the module contract in Rust:
  - `src/module.rs` — the `Module` trait.
  - `src/types.rs` — `ModuleInfo`, `Capability`, `HealthStatus`.
  - `src/events.rs` — `SfuEvent`, `TrackInfo`, `TrackKind`.
  - `src/error.rs` — `ModuleError`.
- `sdk/proto/proto/` — Protobuf definitions, the single source of truth for
  gRPC (`module.proto`: `ModuleRegistry`, `EventStream`; `room.proto`:
  `RoomManagement`).
- `sdk/grpc-adapter/` — the gRPC bridge: `ModuleRegistryService`, `ModuleStore`,
  `RegisteredModule`, `EventBus`, `EventStreamService`, `ForwardHandle`.
- `core/server/src/hooks.rs` — the four SFU hooks and their types (`SfuHooks`,
  `CommandContext`, `CommandResult`, `LifecycleEvent`).
- `bin/vuisio/src/modules.rs` — `wire_hooks_from_store()`: where a capability is
  connected to the hooks. You will add your wiring here.

## The module contract (sdk/traits)

```rust
#[async_trait]
pub trait Module: Send + Sync {
    fn info(&self) -> ModuleInfo;
    async fn on_start(&self) -> Result<(), ModuleError>;
    async fn on_event(&self, event: SfuEvent) -> Result<(), ModuleError>;
    async fn health_check(&self) -> Result<HealthStatus, ModuleError>;
    async fn on_stop(&self) -> Result<(), ModuleError>;
}

pub enum Capability { Recording, Analytics, Stun, Custom(String) }
```

`info()` returns `ModuleInfo { name, version, capabilities: Vec<Capability> }`.

## The four SFU hooks (core/server/src/hooks.rs)

- `on_command(CommandContext) -> CommandResult` — a datachannel message the SFU
  does not recognise as a built-in command.
- `on_media_out(&str, ClientId, &MediaData, Option<&[u8]>, &mut Vec<u8>)` — tap
  outgoing media (called after the local forward), e.g. for archiving.
- `on_media_transform(&str, ClientId, &MediaData, &mut Vec<u8>) -> bool` —
  transform an audio payload before forwarding; return `true` to replace it.
- `on_lifecycle(LifecycleEvent) -> Vec<String>` — join, leave, track open, chat,
  reactions. The returned messages are sent to the client only for
  `ClientJoined`.

`CommandResult` variants: `NotHandled`, `Intercepted`, `Broadcast(String)`,
`BroadcastAndRequestKeyframes(String)`, `BroadcastBinary(Vec<u8>)`,
`ReplyOnly(String)`.

`MediaData` comes from `str0m` (mid, kind, rid, data, params, time, ...).

## Registration flow

At startup the module calls `ModuleRegistry.Register`:

```protobuf
message RegisterRequest {
  string name = 1;
  string version = 2;
  repeated string capabilities = 3;
  string grpc_addr = 4;
  repeated string depends_on = 5;
}
```

The module name must be listed in the `VUISIO_MODULES_ENABLED` environment
variable. The orchestrator replies with a `module_id`, a heartbeat interval and
an optional `config_json`; the module then sends a periodic `Heartbeat`.

## Steps to build a module

1. Add a `Capability` variant in `sdk/traits/src/types.rs` (or use
   `Custom(String)`).
2. Define a gRPC service in `sdk/proto/proto/` if the module needs its own RPCs.
3. Implement a standalone binary (a gRPC server) that follows the contract.
4. Register via `ModuleRegistry.Register` at startup, then heartbeat.
5. Wire the hooks in `bin/vuisio/src/modules.rs` (`wire_hooks_from_store()`):
   resolve the module's gRPC client for its capability, and route the relevant
   hooks to it. This mapping is explicit (hardcoded per capability), so adding a
   capability means editing the orchestrator and recompiling it.
6. Optional UI: drop a bundle in `modules/<name>/ui/dist/`; the orchestrator
   exposes it through its module API.

## Conventions

- English for all code, comments, commits and PRs.
- Conventional commits: feat / fix / refactor / docs / test / chore / perf / ci.
- Rust 2024 edition.
- No punctuation dashes in user-facing text (UI, docs, error messages).

## Build and test

```bash
cargo build --workspace
cargo test --workspace
cargo clippy --workspace -- -D warnings
cargo fmt --all -- --check
```
