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

# Advanced

> Stability tuning, Shadow DOM, CSP, and troubleshooting

# Advanced Configuration

## Stability Tuning

Before capturing page context, the SDK waits for the page to stabilize. This avoids sending incomplete or mid-render snapshots to the AI. The stability system runs a sequence of detection layers, each of which can be toggled independently:

| Layer                  | What It Detects                                               | Default  |
| ---------------------- | ------------------------------------------------------------- | -------- |
| **Network Idle**       | Pending fetch/XHR requests                                    | Enabled  |
| **DOM Mutations**      | DOM changes (via MutationObserver)                            | Enabled  |
| **Loading Indicators** | Spinners, skeletons, `[aria-busy]`                            | Disabled |
| **Frame Readiness**    | Same-origin frame documents reaching `readyState: 'complete'` | Enabled  |
| **Resource Quiet**     | Resource Timing entries settling after the newest completion  | Enabled  |
| **Layout Shifts**      | Visual layout changes (via PerformanceObserver)               | Disabled |
| **Browser Idle**       | CPU idle (via requestIdleCallback)                            | Enabled  |
| **Final Frame**        | One requestAnimationFrame                                     | Enabled  |

<Info>
  Loading indicators and layout shift layers are disabled by default because many sites have persistent loading animations that would cause unnecessary delays. Enable them if your application uses skeleton screens that reliably disappear.
</Info>

### Enabling Additional Layers

```tsx theme={null}
<AgentProvider config={{
  // ...
  stability: {
    layers: {
      loadingIndicators: true,  // Enable loading indicator detection
      layoutShift: true,        // Enable layout shift detection
    },
  },
}}>
```

### Layer-Specific Configuration

Each layer can be configured individually:

```tsx theme={null}
stability: {
  // Global timeouts
  maxTotalWaitMs: 15000,       // Max total time across all layers (default: 15s)
  networkTimeoutMs: 10000,     // Network idle timeout (default: 10s)
  browserIdleTimeoutMs: 500,   // Browser idle timeout (default: 500ms)

  // DOM mutations layer
  domMutations: {
    quietPeriodMs: 800,        // No mutations for this long = stable (default: 800ms)
    excludeSelectors: ['.live-clock', '.notification-badge'],
  },

  // Loading indicators layer (must also enable via layers.loadingIndicators)
  loadingIndicators: {
    selectors: ['[class*="skeleton"]', '.spinner', '[aria-busy="true"]'],
    excludeSelectors: ['.permanent-loader'],
    timeoutMs: 5000,
  },

  // Layout shift layer (must also enable via layers.layoutShift)
  layoutShift: {
    quietPeriodMs: 200,        // No shifts for this long = stable (default: 200ms)
    ignoreThreshold: 0.01,     // Ignore tiny shifts (default: 0.01)
  },
}
```

### Disabling Stability Checks

For testing or very simple pages, you can disable layers:

```tsx theme={null}
stability: {
  layers: {
    networkIdle: false,
    domMutations: false,
    browserIdle: false,
    finalFrame: false,
  },
}
```

<Warning>
  Disabling stability layers may cause the AI to receive incomplete page context. Only disable for testing or if you're certain the page is fully rendered.
</Warning>

### After Reload Handling

If your application triggers full page reloads during guided steps, enable the `afterReload` option to preserve state:

```tsx theme={null}
stability: {
  afterReload: {
    enabled: true,          // Off unless explicitly enabled
    pendingTTL: 30000,      // How long pending state stays valid (default: 30000ms)
    storageKey: 'my-app-moss-pending',  // Override the storage key holding pending state
  },
}
```

| Field        | Type      | Default     | Description                                                      |
| ------------ | --------- | ----------- | ---------------------------------------------------------------- |
| `enabled`    | `boolean` | `false`     | Preserve pending guide state across a full page reload           |
| `pendingTTL` | `number`  | `30000`     | Milliseconds a pending marker stays valid before it is discarded |
| `storageKey` | `string`  | SDK default | Storage key used to hold the pending marker                      |

***

## Shadow DOM

The SDK renders all UI inside a Shadow DOM attached to `document.body`. This means:

* **Your CSS cannot affect Moss UI** — styles are fully encapsulated
* **Moss CSS cannot affect your app** — no style leakage
* **DOM queries from your app won't find Moss elements** — `document.querySelector` won't match elements inside the shadow root

<Tip>
  If you're using automated testing tools (Cypress, Playwright, etc.), you'll need to pierce the shadow DOM to interact with Moss elements. Look for the `#moss-shadow-host` host element (`#clippy-shadow-host` in SDK versions up to 0.16).
</Tip>

***

## Content Security Policy (CSP)

If your application uses strict CSP headers, you may need to allow:

| Directive     | Value                         | Reason                |
| ------------- | ----------------------------- | --------------------- |
| `connect-src` | Your Moss backend URL         | API requests          |
| `script-src`  | CDN URL (if using script tag) | SDK script            |
| `style-src`   | `'unsafe-inline'`             | Shadow DOM styles     |
| `img-src`     | `blob:` `data:`               | Screenshot processing |

Example CSP header:

```
Content-Security-Policy:
  connect-src 'self' https://moss-api.viamoss.ai;
  style-src 'self' 'unsafe-inline';
  img-src 'self' blob: data:;
```

***

## DOM Observation Scope

By default, the SDK observes `document.body` for changes. To limit observation to a specific part of your page:

```tsx theme={null}
<AgentProvider config={{
  // ...
  observeTargetSelector: '#main-content',
}}>
```

This is useful when:

* Your app has a complex sidebar or header that shouldn't trigger re-captures
* You want to reduce noise from unrelated DOM changes
* The assistant should only help with a specific section of the page

***

## Native Modal Dialogs

When your application opens a native `<dialog>` with `showModal()`, the
browser makes everything outside the dialog inert — including the Moss widget.
The user can't click the assistant, and an in-progress guide can't continue.

If your application uses `showModal()`, install the modal dialog escape with
the element your integration mounts the widget into:

```tsx theme={null}
import { installModalDialogEscape } from '@viamoss/moss-sdk';

const uninstall = installModalDialogEscape(widgetMountElement);

// Later, if you tear the widget down:
uninstall();
```

While a modal dialog is open, the SDK host is temporarily moved into the
dialog's tree (and promoted above it) so the widget stays interactive and
keeps its viewport position. When the dialog closes, the host is restored to
its original place in the DOM. The returned function uninstalls the listeners
and restores the host.

<Warning>
  Pass an element your integration owns outright — the container you mounted
  the Moss widget into. Never pass a node managed by your application's
  framework: the escape re-parents the element, which would fight the
  framework's own rendering.
</Warning>

**Limitations:**

* Dialogs rendered inside shadow roots are not tracked.
* In browsers without the Popover API, the widget stays interactive but may
  render clipped inside dialogs that have `overflow: hidden` or a transform.

***

## Redacting Sensitive Content

### Declarative Redaction with `data-moss-redact`

Mark any HTML element with the `data-moss-redact` attribute to redact it from all data sent to the Moss backend. This is the simplest way to protect sensitive content.

```html theme={null}
<div data-moss-redact>
  <p>Account balance: $12,340.56</p>
  <p>SSN: 123-45-6789</p>
</div>
```

**What gets redacted:**

* **Text content** — replaced with `[REDACTED]` in the structured context sent to the backend
* **Sensitive attributes** — `value`, `placeholder`, `title`, `alt`, `aria-label`, `href`, `src`, and `name` are all redacted
* **Child elements** — all descendants of a redacted element are excluded

Redaction applies to the marked element and any of its ancestors up to 10 levels deep, so you can mark a container to redact everything inside it.

```html theme={null}
<!-- All form fields inside this section are redacted -->
<section data-moss-redact>
  <input type="text" placeholder="Card number" />
  <input type="text" placeholder="CVV" />
</section>
```

### Input Redaction (default on)

All input-type elements — `input`, `textarea`, `select`, and
`contenteditable` regions — have their values redacted by default. What a
user has typed into a form never leaves the browser as page context.

To opt a specific field out when its content is known to be safe and useful
to the assistant (for example, a search box), mark it with
`data-moss-unredact`:

```html theme={null}
<input type="search" data-moss-unredact placeholder="Search products…" />
```

To disable input redaction entirely, set `redactAllInputs: false`:

```tsx theme={null}
<AgentProvider config={{
  // ...
  redactAllInputs: false,  // default: true
}}>
```

<Warning>
  Keep `redactAllInputs` enabled unless you have reviewed every form in your
  application. With it disabled, typed form values are included in the page
  context sent to Moss.
</Warning>

### Redaction by CSS Selector

If your application already marks sensitive elements for another tool (for
example FullStory's `.fs-exclude` / `.fs-mask` classes), reuse those markers
instead of adding `data-moss-redact` everywhere. Elements matching
`redactionSelectors` are redacted identically to `data-moss-redact`:

```tsx theme={null}
<AgentProvider config={{
  // ...
  redactionSelectors: ['.fs-exclude', '.fs-mask', '[data-private]'],
}}>
```

`data-moss-redact` is always recognized regardless of this setting. If an
element matches both an allow rule and a redact rule, the redact rule wins.

### What redaction covers

Redaction applies to the page text and structure sent as context and to the
interaction reports recorded during step-by-step guidance. It does not alter
chat messages the user types, screenshots, session recordings, or screen
history — disable those features individually if their output is not
permitted in your environment.

### Programmatic Sanitization

For more complex sanitization logic, use `pageSanitizationScript` to modify a cloned copy of the DOM before capture:

```tsx theme={null}
<AgentProvider config={{
  // ...
  pageSanitizationScript: (doc) => {
    // Remove credit card fields
    doc.querySelectorAll('[data-sensitive]').forEach(el => {
      el.textContent = '[REDACTED]';
    });
    // Remove tracking pixels
    doc.querySelectorAll('img[width="1"]').forEach(el => el.remove());
  },
}}>
```

<Warning>
  Both methods only affect what the AI sees. The live DOM is never modified — your users won't see any changes.
</Warning>

***

## Troubleshooting

### Assistant button doesn't appear

1. Check the browser console for `[MossSDK]` errors
2. Verify `applicationId` matches a valid application in the Dashboard
3. Confirm `apiUrl` is reachable from the browser
4. Check that your JWT signing key is active and not revoked

### "Failed to fetch config" error

* The SDK couldn't reach the backend. Check `apiUrl` and network/CORS settings.
* If using CSP, ensure `connect-src` includes the backend URL.

### Screenshots are blank or incomplete

* Try switching `screenshotMode` to `'viewport'`
* Increase `stability.domMutations.quietPeriodMs` if the page has slow animations
* Check if iframes or cross-origin content is blocking capture

### Styles conflict with host application

This shouldn't happen — the SDK uses Shadow DOM isolation. If you see conflicts:

* Check if your app is using `!important` on `*` or `body` selectors
* Verify no JavaScript is modifying the shadow root

### Debug mode

Enable verbose logging to diagnose issues:

```tsx theme={null}
import { LogLevel } from '@viamoss/moss-sdk';

<AgentProvider config={{
  // ...
  debugMode: true,
  logLevel: LogLevel.DEBUG,
}}>
```

This logs stability layer timing, network requests, context capture details, and API responses to the browser console.
