> ## Documentation Index
> Fetch the complete documentation index at: https://sdk.libratech.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# How it works

> The SDK's lifecycle, its isolation model, and what survives between calls

## One iframe, positioned over your element

`Libra.init()` creates a fixed-position wrapper (`div[data-libra-sdk]`) containing an iframe, and appends it to `document.body` (or to your `mountTarget`). The iframe loads from a `blob:` URL with the entire chat application and its styles inlined, so:

* **Zero external requests at load time.** The bundle is self-contained.
* **Full CSS isolation.** Host styles cannot affect the widget and vice versa.
* **Host-controlled CSP.** The blob iframe is generated by your page and inherits your origin. Because of Safari-specific CSP enforcement, SDK-enabled pages must allow same-origin `blob:` frames; see [Security headers](/guides/security-headers).

`Libra.attach(elementOrId)` does not move the iframe in the DOM. It only updates the wrapper's CSS position, continuously synced to the target's bounding box with `requestAnimationFrame`, so it follows scrolling, layout shifts and animations. That is why the chat session, scroll position and in-progress streaming are preserved across `detach()` / `attach()`, even when you attach to a different container after an SPA route change.

## Lifecycle

| Call                   | What happens                                                                                                                                   | Network     |
| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | ----------- |
| `Libra.init(config)`   | Creates the wrapper and iframe. Registers locales and callbacks.                                                                               | None        |
| `Libra.attach(target)` | Shows the widget over `target`. On the **first** call, sends the config into the iframe and starts the session: authentication, then the chat. | Starts here |
| `Libra.detach()`       | Hides the widget. The iframe and session stay alive.                                                                                           | None        |
| `Libra.attach(other)`  | Re-shows the same session over another element.                                                                                                | None        |
| `Libra.destroy()`      | Removes the iframe and cleans up. Call `init()` again to recreate.                                                                             | None        |

Call `init()` once at app startup, after `document.body` exists. Set `mountTarget` at init only: an iframe cannot be reparented later without the browser reloading it, which would destroy the session.

## Who owns the header

The SDK renders its Libra headbar (wordmark and close button) **only when you pass `onClose`**. Clicking close calls your callback; the host owns the close behaviour, typically hiding the product-owned wrapper (a side panel or modal) and calling `Libra.detach()`.

If your product already renders a header or close control around the widget, omit `onClose` and call `Libra.detach()` from your own control. Header ownership covers only this outer bar; the authenticated chat screen still renders its own controls.

## Authentication in one paragraph

By default the SDK handles authentication itself through an Auth0 popup, with **Sign in** and **Create account** (onboarding runs inside the iframe). Tokens live in the iframe's `localStorage`, namespaced under `libra:` so they never collide with your keys, and later loads skip the auth screen while a valid token exists. No auth configuration is needed from the host. Products that must not let the browser hold a Libra token use [proxy mode](/guides/proxy-mode) instead, where your BFF exchanges the user's product token server-side.

## Storage and messaging

The blob iframe **inherits your page's origin**, so SDK state — the auth token, the locale, the active chat id — is written into **your own origin's `localStorage`**, namespaced under a `libra:` prefix. The isolation is namespacing, not a separate storage partition. Three consequences worth knowing:

* `libra:` keys show up in your storage inspector.
* Clearing site data signs the user out.
* A host that calls `localStorage.clear()` wipes the SDK session.

CSS and DOM isolation are unaffected — those are real, and provided by the iframe.

Configuration reaches the iframe via `postMessage`, not an inline script, so the iframe HTML is static and CSP hashes remain stable across config changes.

## Multi-page hosts

On a fresh page load the SDK **reopens the user's last active chat automatically**, from a per-user key in that same storage. It is cleared when the user starts a new chat, and deliberately kept across sign-out so the same user gets their session back when they sign in again. Research-source selections are preserved too.

The one thing your page must re-supply on every navigation is the document suggestion — `setDocumentSuggestion()` state is in-memory only and does not survive a page load. See [Document suggestions](/guides/document-suggestions).

<Card title="Next: Embedding guide" icon="plug" href="/guides/embedding">
  Containers, stacking context, and the constraints on `mountTarget`.
</Card>
