# Bloby Chat Architecture

## Network Topology

```
Browser
  |
  |  https://bot-name.at.bloby.bot
  v
Bloby Relay (cloud)
  |
  |  Cloudflare Quick Tunnel
  v
Supervisor (localhost:3000)
  |
  ├── Worker (localhost:3001)      — DB, settings, AI providers
  ├── Dashboard Vite (localhost:3002) — dev server for workspace/client
  └── Backend (localhost:3004)     — user's app backend
```

## How the Chat Loads

The dashboard (`workspace/client/`) loads at the root URL. It injects `widget.js`, which creates:

1. A floating bubble (the Bloby avatar)
2. A slide-out panel containing an **iframe** at `/bloby/`

The iframe loads `dist-bloby/bloby.html`, which is the pre-built Bloby chat SPA. This SPA is completely independent from the dashboard — it has its own React tree, its own styles, and its own WebSocket connection.

```
Dashboard (main page)
  └── widget.js
        ├── Bubble (click to open)
        └── Panel
              └── <iframe src="/bloby/">   ← Bloby chat lives here
                    ├── bloby-main.tsx
                    ├── InputBar, MessageList, etc.
                    └── OnboardWizard (opened from settings menu)
```

## The Relay Problem

When a user registers a handle (e.g. `bug.at.bloby.bot`), the relay proxies all HTTP traffic through Cloudflare's tunnel to the local supervisor. This works transparently for:

- **GET requests** — settings, onboard status, static assets
- **WebSocket** — chat messages, heartbeats, real-time events

However, **POST requests from the chat iframe fail** (502 / timeout). The relay + tunnel chain cannot reliably forward HTTP POST bodies originating from an iframe context. The exact cause is in the relay/tunnel infrastructure — the request never reaches the supervisor.

This does NOT affect:
- The initial onboard (`/bloby/onboard.html` in a separate iframe, runs before the relay is configured)
- Direct `localhost` access
- GET requests from the chat iframe
- WebSocket messages from the chat iframe

## The Fix: WebSocket as a Sidecar Channel

Since the WebSocket connection works reliably from the chat iframe, any mutation that the chat needs to perform (saving settings, etc.) is sent over WebSocket instead of HTTP POST.

```
Chat Wizard                    Supervisor WS Handler           Worker
    |                               |                           |
    |-- ws: settings:save --------->|                           |
    |                               |-- POST /api/onboard ----->|
    |                               |   (localhost, no relay)   |
    |                               |<-- { ok: true } ---------|
    |<-- ws: settings:saved --------|                           |
```

The supervisor's WebSocket handler receives the `settings:save` message and makes a **local** HTTP POST to the worker (`localhost:3001`). This completely bypasses the relay.

### When to use WebSocket vs HTTP

| From where | GET | POST/PUT | Notes |
|---|---|---|---|
| Initial onboard | `fetch()` | `fetch()` | Runs before relay exists, direct tunnel works |
| Chat iframe | `fetch()` / `authFetch()` | **WebSocket** | POST through relay fails |
| Dashboard | `fetch()` | `fetch()` | Not in an iframe, relay handles it fine |

## Key Files

| File | Purpose |
|---|---|
| `supervisor/widget.js` | Creates the chat iframe + bubble on the dashboard |
| `supervisor/chat/bloby-main.tsx` | Chat app entry point, WS connection, wizard integration |
| `supervisor/chat/OnboardWizard.tsx` | Setup wizard (uses `onSave` prop for WS saves) |
| `supervisor/chat/src/lib/ws-client.ts` | WebSocket client with reconnect + message queue |
| `supervisor/index.ts` | Supervisor HTTP server + WS handler (`settings:save`) |
| `worker/index.ts` | Worker API (`POST /api/onboard`, settings DB) |
