> ## Documentation Index
> Fetch the complete documentation index at: https://whatsapp-rust.jlucaso.com/llms.txt
> Use this file to discover all available pages before exploring further.

# June 11, 2026 — Bot API overhaul (breaking)

> Lifecycle simplification, typed event registrars, single-dependency consumption, and messaging sugar ahead of 1.0.

PR [#852](https://github.com/oxidezap/whatsapp-rust/pull/852) is a focused breaking-change pass over the public bot API while such changes are still cheap. You will need to migrate your code, but the migration is mechanical. See the [breaking changes](#breaking-changes) section below.

## New features

### One dependency is enough

`whatsapp-rust` now re-exports `wacore`, `wacore_binary`, and `waproto` wholesale. A consumer needs exactly one `Cargo.toml` line:

```toml theme={null}
[dependencies]
whatsapp-rust = "0.6"
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
```

All sub-crate paths (`whatsapp_rust::wacore::...`, `whatsapp_rust::waproto::whatsapp`, etc.) are reachable through the main crate, including when pinning a git revision.

`UreqHttpClient` moved from the misplaced `whatsapp_rust::transport` to `whatsapp_rust::http`. A new `whatsapp_rust::prelude` covers the common bot path in one import line (including `wa` as the protobuf alias):

```rust theme={null}
use whatsapp_rust::prelude::*;
```

### Simplified lifecycle

`Bot::run(self)` now runs the bot on the current task with a **single** `await`. `Bot::spawn(self)` starts it in the background and returns a `BotHandle`:

```rust theme={null}
// Foreground — blocks until logout or disconnect
bot.run().await;

// Background — returns immediately
let handle = bot.spawn();
let client = handle.client();           // full Client API
handle.shutdown().await;                // graceful: flushes state, then stops
handle.abort();                         // escape hatch, skips flush
```

Awaiting a `BotHandle` resolves to `()` (was `Result<(), Canceled>`). Dropping the handle still aborts the task — the e2e harness relies on this.

### Builder defaults

With the default cargo features, transport, HTTP client, and runtime are pre-filled (Tokio WebSocket, ureq, Tokio), so only the backend has to be provided:

```rust theme={null}
let bot = Bot::builder()
    .with_backend(SqliteStore::new("whatsapp.db").await?)
    .build()
    .await?;
```

`with_*` setters are now available in every typestate, so they also work as overrides. The typestate markers got names (`MissingBackend`, `MissingTransport`, `MissingHttpClient`, `MissingRuntime`) — a missing-field compile error now names which field is missing.

`with_backend` accepts `impl Backend + 'static` (no caller-side `Arc::new`). `with_backend_arc` accepts an already-shared `Arc<dyn Backend>`.

### Typed event registrars

Dedicated builder methods cover the common event path — no `match &*event` needed:

```rust theme={null}
Bot::builder()
    .on_message(|ctx| async move {
        // ctx: MessageContext — has reply, reply_quoting, react, send_message
    })
    .on_qr_code(|code, timeout| async move { /* ... */ })
    .on_pair_code(|code, timeout| async move { /* ... */ })
    .on_connected(|client| async move { /* ... */ })
    .on_logged_out(|info| async move { /* ... */ })
```

`on_event` / `on_event_for` are unchanged as catch-alls. `with_event_handler` registers a struct-based `EventHandler` directly on the bus for stateful handlers (eliminates the clone-dance that closure captures force on consumers).

All handlers **accumulate** — registering a second handler now runs both instead of silently replacing the first.

The bus still skips materializing events nobody wants: the adapter registers the union of all handler interests and filters per handler at dispatch.

### Messaging sugar

New helpers for the common send paths:

```rust theme={null}
// On MessageContext (inside on_message)
ctx.reply("pong").await?;
ctx.reply_quoting("pong").await?;

// On Client
client.send_text(&jid, "hello").await?;

// Static constructors on wa::Message (via MessageBuilderExt)
use whatsapp_rust::prelude::*;
let msg = wa::Message::text("hello");
let msg_with_quote = wa::Message::text_with_context("hello", ctx.build_quote_context());
```

The raw `ctx.send_message(wa::Message { ... })` path is unchanged for advanced cases.

### `BotBuilderError` typed

`BotBuilderError::Other(anyhow)` is gone. The only variant is now `Store(StoreError)` — the one error `build()` can actually produce.

## Breaking changes

| Old                                              | New                                                                                 |
| ------------------------------------------------ | ----------------------------------------------------------------------------------- |
| `bot.run().await?.await?`                        | `bot.run().await` (foreground) or `let handle = bot.spawn()` (background)           |
| `.with_backend(Arc::new(store))`                 | `.with_backend(store)` — `Arc` wrapping is internal                                 |
| `.with_backend(arc)` for a shared Arc            | `.with_backend_arc(arc)`                                                            |
| `use whatsapp_rust::transport::UreqHttpClient`   | `use whatsapp_rust::http::UreqHttpClient`                                           |
| `bot::Missing` typestate marker                  | `bot::MissingBackend` / `MissingTransport` / `MissingHttpClient` / `MissingRuntime` |
| `BotBuilderError::Other(anyhow)`                 | removed; only `BotBuilderError::Store(StoreError)`                                  |
| Registering two handlers — second replaces first | Both run; handlers accumulate                                                       |

### Migration guide

**Lifecycle:**

```rust theme={null}
// Before
let mut bot = builder.build().await?;
let handle = bot.run().await?;
handle.await?;

// After (foreground)
let bot = builder.build().await?;
bot.run().await;

// After (background / ctrl-c handling)
let bot = builder.build().await?;
let handle = bot.spawn();
tokio::signal::ctrl_c().await?;
handle.shutdown().await;
```

**Backend:**

```rust theme={null}
// Before
let backend = Arc::new(SqliteStore::new("whatsapp.db").await?);
Bot::builder().with_backend(backend)

// After
Bot::builder().with_backend(SqliteStore::new("whatsapp.db").await?)

// After (already-shared Arc)
Bot::builder().with_backend_arc(existing_arc)
```

**`UreqHttpClient` import:**

```rust theme={null}
// Before
use whatsapp_rust::transport::UreqHttpClient;

// After
use whatsapp_rust::http::UreqHttpClient;
```

**Builder dependencies — drop sibling crates from `Cargo.toml`:**

```toml theme={null}
# Before (all required)
whatsapp-rust = "0.6"
whatsapp-rust-sqlite-storage = "0.6"
whatsapp-rust-tokio-transport = "0.6"
whatsapp-rust-ureq-http-client = "0.6"
wacore = "0.6"
waproto = "0.6"

# After (one line is enough)
whatsapp-rust = "0.6"
tokio = { version = "1", features = ["macros", "rt-multi-thread", "signal"] }
```
