> For the complete documentation index, see [llms.txt](https://shoppad.gitbook.io/yedric/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://shoppad.gitbook.io/yedric/developers/advanced-configuration.md).

# Advanced Configuration

Everything here assumes you have the basic embed working. See [Embedding the Widget](/yedric/developers/embedding-widget.md) first.

## Design and layout: configure in the dashboard

Design and layout are not configured with attributes on `<yedric-widget>`. All of it lives in the dashboard under **Widget → Configure**, saved per agent and delivered to every embed automatically with `widget.js`. Click **Save** to publish, and use the **Preview** tab to try changes first.

The Configure tab covers:

* **General**: Theme, Title, Side (beacon & panel), Default state, Show Page prompts when beacon is closed, Prompt suggestion, Re-open when page changes, Hide beacon, Beacon position
* **Features**: File uploads, Microphone (speech-to-text), Live chat, Response voting, Show tool activity
* **Mascot**: Beacon icon, Agent chat icon, User Chat icon, Beacon tooltip, and mascot Videos (placeholder image plus Waving, Thinking, Thumbs up, and Presenting clips). The **Build character** tab can generate this whole set with AI, see [Build a Character](/yedric/going-further/build-character.md).
* **Display**: Panel width, Border style, Suggestion style, Prompt placeholder text, border radii, Font family, Base font size, Greeting, Greeting description, and the full color palette (base theme, title bar, buttons, prompt input, suggestions, avatars and message bubbles, greeting)
* **Advanced**: Features (comma-separated), Add margin to host body when sidebar is open, Debug mode

See [Embedding the Widget → Additional customizations](/yedric/developers/embedding-widget.md#additional-customizations) for the full setting-by-setting reference.

A few settings worth calling out:

### Hide beacon

Found under **General**. When the panel is closed, no beacon button is shown, so you must open the widget programmatically:

```js
window.Yedric.open();
// or
document.querySelector('yedric-widget').open();
```

This is the right setup for host apps that want to trigger the assistant from their own UI (a menu item, a help button, etc.).

### Add margin to host body when sidebar is open

Found under **Advanced**. When enabled, opening the panel adds a margin to `document.body` matching the panel width, so the host page content shifts to make room rather than being covered. When closed, the margin is animated back to 0 and cleared.

This is useful for:

* Dashboard previews where you want to see both the host app and the assistant side-by-side.
* Host pages where overlaying the content with a backdrop is visually jarring.

When disabled, the panel overlays the host content with a dimmed backdrop.

On narrow viewports (640px and under) the panel takes the full viewport width and the body margin is not applied.

### Default state on phones

Found under **General**. An embed with **Default state** set to Open starts closed on viewports 640px and narrower, because the open panel there is full-width and full-height and would replace the page. The visitor gets the beacon instead, and one tap opens the same panel. Nothing changes on tablets and desktops.

Four embeds keep opening on a phone, because for them closed is either unreachable or not what was asked for: `fab="false"` (no beacon to reopen with), a `resume-session-id` handoff, an interrupted live chat that is being resumed, and the dashboard preview.

### Prompt suggestions on the closed beacon

Found under **General**, and only when **Default state** is Closed. With **Show Page prompts when beacon is closed** checked, the widget renders the current page's matched Page prompts as clickable bubbles beside the closed beacon. Clicking one opens the chat and sends the prompt. Dismissing the cluster hides it for an hour in that browser. While the setting is on, the beacon tooltip is suppressed, since the bubbles occupy its spot.

The **Prompt suggestion** field adds an optional bubble of your own on top, shown with the assistant's avatar. The cluster is capped at four bubbles in total. To supply a page-specific prompt from the host page instead, set `prompt-suggestion` on the element:

```html
<yedric-widget
  key="yk_abc…"
  agent="agt_123"
  prompt-suggestion="Questions about this order?"
></yedric-widget>
```

The attribute only overrides the text. The feature itself is turned on in the dashboard, so an embed whose configuration has it off shows nothing.

### Re-open when page changes

Found under **General**, off by default. When on, a visitor who follows a link with the chat open finds it open again on the next page, on the same conversation, with no slide-in. Closing the panel before leaving clears that, so a visitor who is done does not get the panel back on the next page. The handoff is scoped to the browser tab.

Set `reopen-on-page-change` on the element to override the dashboard setting for one embed:

```html
<yedric-widget key="yk_abc…" agent="agt_123" reopen-on-page-change="true"></yedric-widget>
```

`reopen-on-page-change="false"` turns it off for that embed even when the dashboard setting is on.

### Draggable beacon

Visitors can drag the beacon to another corner when it lands on something they need (a cookie bar, a sticky cart, a call to action). The drop snaps to the nearest corner, the docked panel follows the beacon, and the choice is stored per agent in the visitor's `localStorage` so it survives a page load. A drag only starts after the pointer has moved a short distance, so ordinary clicks still open the panel.

To keep the beacon where you placed it on a given embed:

```html
<yedric-widget key="yk_abc…" agent="agt_123" fab-draggable="false"></yedric-widget>
```

Dragging is also off when the beacon is hidden and in the dashboard preview.

### Letting the assistant navigate to other origins

The assistant has a built-in action that can take the visitor to the page it is talking about, described in [Actions](/yedric/going-further/actions.md). The widget decides whether a target is allowed, and the policy is deliberately narrow: `http` and `https` only, and the same origin as the embedding page. A relative path is always same-origin, so it is always allowed. Anything refused is reported back to the assistant, which can link the page in its reply instead.

To let it navigate to other sites you control, list their origins in `navigate-allow`, separated by spaces or commas. A bare hostname is treated as `https`:

```html
<yedric-widget
  key="yk_abc…"
  agent="agt_123"
  navigate-allow="https://docs.example.com, https://help.example.com"
></yedric-widget>
```

Matching is on the full origin (scheme, host, and port), never on a suffix, so `https://example.com` does not admit `https://evil-example.com` or `https://app.example.com`. The attribute is read from the element rather than from the dashboard so the policy is visible in the page markup that opts into it.

## Iframe and cross-frame embedding

The widget renders inside Shadow DOM, so hosting it in an iframe is already well-isolated from the host page. When the widget needs to reach outside the iframe (e.g. to signal open/close to a parent dashboard), set:

```html
<yedric-widget
  key="yk_abc…"
  agent="agt_123"
  parent-origin="https://your-dashboard.example.com"
></yedric-widget>
```

`parent-origin` is the target origin used by the widget's internal `postMessage` bridge. Setting it implicitly opts in to that bridge.

## Shopify App Bridge auth

When your assistant uses a custom HTTP tool server that's hosted inside a Shopify-embedded app, enable **Shopify App Bridge authentication** (OAuth 2.1) on that custom server in the Yedric dashboard. With App Bridge auth turned on, the widget:

1. Queries `GET /api/embed/agents/:agentId/widget-auth` on load to check whether any tool server wants App Bridge.
2. If yes, calls `window.shopify.idToken()` (or similar) to mint a session token and sends it as `Authorization: Bearer <idToken>`.
3. Appends `?shop=<shop>` to API requests. The shop comes from (in order): the `shop` attribute, the `shop` URL query param, or `window.shopify.config.shop`.

You generally do **not** need to set `auth-token` manually for Shopify embeds. Let App Bridge handle it.

```html
<yedric-widget
  key="yk_abc…"
  agent="agt_123"
  shop="example-store.myshopify.com"
></yedric-widget>
```

## Client-side tool execution

Normally a custom HTTP tool is called server-to-server: the Yedric backend POSTs to the tool URL you registered in the dashboard. For some tools you want the handler to run **in the host page** instead, to call host-app JS, read page state, open a modal, and so on.

{% hint style="info" %}
The `X-Yedric-Client-Tools` header is sent automatically by the widget when you call `registerClientTool()`. You only need to set it manually when calling `POST /api/chat` from your own client.
{% endhint %}

### Flow

1. Before the first chat request, register a handler for the tool by its exact name:

   ```js
   window.Yedric.registerClientTool('Contact_Support', (ctx) => {
     // ctx = { toolCallId, toolName, args }
     window.HostApp.openSupportModal();
     return 'Support modal opened.';
   });
   ```
2. The widget automatically sends `X-Yedric-Client-Tools: Contact_Support,OtherTool,...` on each `POST /api/chat` request.
3. When the assistant calls a delegated tool, the server emits a `tool_request` data part with `clientDelegate: true` and **skips** the outbound HTTP call.
4. The widget runs your handler, JSON-serializes the return value (or catches the error), and POSTs the result to `/api/chat/client-tool-result` with `{ toolCallId, result }`.
5. The server unblocks and the model continues with the tool result.

### Handler contract

```ts
type ClientToolHandlerContext = {
  toolCallId: string;
  toolName: string;
  /** Arguments as sent to the custom HTTP tool (POST body or derived for GET). */
  args: unknown;
};

type Handler = (ctx: ClientToolHandlerContext) => Promise<unknown> | unknown;
```

* The return value must be JSON-serializable (or a Promise of one). Return a plain object or string the model will read back.
* If the handler throws, the widget returns `{ error: message }` as the tool result. The model sees the error and can recover.
* The tool name you register must **exactly match** the tool's name on the custom HTTP server definition in the dashboard. No handler → the server returns an error tool result explaining the mismatch.

### Real-world example

Opening a support beacon from a host app:

```js
window.Yedric.registerClientTool('Contact_Support', () => {
  if (window.HostApp && window.HostApp.openSupportBeacon) {
    window.HostApp.openSupportBeacon();
  }
  return 'Contact us form opened in the left corner.';
});
```

The model can now recommend "I've opened the support form for you" and the user sees the beacon open.

### Async handler example

```js
window.Yedric.registerClientTool('Get_Current_Cart', async ({ args }) => {
  const res = await fetch('/api/cart.json');
  if (!res.ok) throw new Error(`Cart fetch failed: ${res.status}`);
  const cart = await res.json();
  return {
    itemCount: cart.item_count,
    total: cart.total_price,
    items: cart.items.map((i) => ({ title: i.title, qty: i.quantity })),
  };
});
```

### Timeouts and horizontal scaling

* The server waits up to **120 seconds** for the `/api/chat/client-tool-result` POST. If it doesn't arrive in time the step completes with an error-shaped tool result.
* Pending results are tracked **in memory per Node process**. If you run multiple backend replicas without sticky routing, the client-tool-result POST may land on a different process than the one waiting, causing the delegation to hang. Use a shared store (Redis) or sticky sessions to work around this.

### Unregistering

```js
window.Yedric.unregisterClientTool('Contact_Support');
```

## Programmatically open the sidebar

The widget exposes both a global API and element methods. In a React/Vue/Svelte host app you can open the panel from anywhere:

```js
// Existence guard: widget.js loads async and might not be ready yet
if (typeof window.Yedric !== 'undefined' && typeof window.Yedric.open === 'function') {
  window.Yedric.open();
}
```

Three shapes:

| Call                              | Behavior                                                                                          |
| --------------------------------- | ------------------------------------------------------------------------------------------------- |
| `Yedric.open()`                   | Open the panel. No message sent. Existing thread is preserved.                                    |
| `Yedric.open('How do I refund?')` | Open the panel and send `'How do I refund?'` as a user message. Clears the existing thread first. |
| `Yedric.open('Status?', true)`    | Open the panel, send the message, and keep existing messages (`persist=true`).                    |

The same behavior is available on the element (useful when you have multiple widget instances, though that's rare):

```js
document.querySelector('yedric-widget').open('Hello');
```

See [JavaScript API](/yedric/developers/javascript-api.md) for the full `Yedric.*` documentation.

## Debug logging

Enable **Debug mode** in **Widget → Configure → Advanced** to turn on diagnostics for every embed, or set `debug="true"` on a single embed:

```html
<yedric-widget key="yk_abc…" agent="agt_123" debug="true"></yedric-widget>
```

When debug is on, the widget `console.log`s:

* Secure mode state on startup.
* Each route/URL change: current `url`/`pathname`, matched **site settings** (id, name, matchUrl), merged **suggestions** with `promptPreview`.
* Client tool delegation: tool name, handler presence, args preview (truncated), POST status to `/api/chat/client-tool-result`.

`console.warn`s for errors (e.g. handler threw, client-tool-result POST failed) are always enabled regardless of this flag. Debug is off by default and should not ship to end users.
