> ## 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.

# 고급

> 안정성 튜닝, Shadow DOM, CSP 및 문제 해결

# 고급 설정

## 안정성 튜닝

페이지 컨텍스트를 캡처하기 전에 SDK는 페이지가 안정화될 때까지 대기합니다. 이를 통해 불완전하거나 렌더링 중인 스냅샷이 AI에 전송되는 것을 방지합니다. 안정성 시스템은 여러 감지 레이어를 순차적으로 실행하며, 각 레이어는 개별적으로 켜고 끌 수 있습니다:

| 레이어                    | 감지 대상                                      | 기본값  |
| ---------------------- | ------------------------------------------ | ---- |
| **Network Idle**       | 대기 중인 fetch/XHR 요청                         | 활성화  |
| **DOM Mutations**      | DOM 변경 (MutationObserver 사용)               | 활성화  |
| **Loading Indicators** | 스피너, 스켈레톤, `[aria-busy]`                   | 비활성화 |
| **Frame Readiness**    | 동일 출처 프레임 문서가 `readyState: 'complete'`에 도달 | 활성화  |
| **Resource Quiet**     | 마지막 리소스 완료 이후 Resource Timing 항목이 잠잠해짐     | 활성화  |
| **Layout Shifts**      | 시각적 레이아웃 변경 (PerformanceObserver 사용)       | 비활성화 |
| **Browser Idle**       | CPU 유휴 (requestIdleCallback 사용)            | 활성화  |
| **Final Frame**        | requestAnimationFrame 1회                   | 활성화  |

<Info>
  로딩 인디케이터와 레이아웃 시프트 레이어는 기본적으로 비활성화되어 있습니다. 많은 사이트에 지속적인 로딩 애니메이션이 있어 불필요한 지연을 유발할 수 있기 때문입니다. 애플리케이션이 확실히 사라지는 스켈레톤 스크린을 사용하는 경우 활성화하세요.
</Info>

### 추가 레이어 활성화

```tsx theme={null}
<AgentProvider config={{
  // ...
  stability: {
    layers: {
      loadingIndicators: true,  // 로딩 인디케이터 감지 활성화
      layoutShift: true,        // 레이아웃 시프트 감지 활성화
    },
  },
}}>
```

### 레이어별 설정

각 레이어를 개별적으로 설정할 수 있습니다:

```tsx theme={null}
stability: {
  // 글로벌 타임아웃
  maxTotalWaitMs: 15000,       // 모든 레이어의 최대 총 대기 시간 (기본값: 15초)
  networkTimeoutMs: 10000,     // 네트워크 유휴 타임아웃 (기본값: 10초)
  browserIdleTimeoutMs: 500,   // 브라우저 유휴 타임아웃 (기본값: 500ms)

  // DOM 뮤테이션 레이어
  domMutations: {
    quietPeriodMs: 800,        // 이 시간 동안 변경 없음 = 안정 (기본값: 800ms)
    excludeSelectors: ['.live-clock', '.notification-badge'],
  },

  // 로딩 인디케이터 레이어 (layers.loadingIndicators도 활성화해야 함)
  loadingIndicators: {
    selectors: ['[class*="skeleton"]', '.spinner', '[aria-busy="true"]'],
    excludeSelectors: ['.permanent-loader'],
    timeoutMs: 5000,
  },

  // 레이아웃 시프트 레이어 (layers.layoutShift도 활성화해야 함)
  layoutShift: {
    quietPeriodMs: 200,        // 이 시간 동안 시프트 없음 = 안정 (기본값: 200ms)
    ignoreThreshold: 0.01,     // 작은 시프트 무시 (기본값: 0.01)
  },
}
```

### 안정성 검사 비활성화

테스트용이나 매우 단순한 페이지의 경우 레이어를 비활성화할 수 있습니다:

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

<Warning>
  안정성 레이어를 비활성화하면 AI가 불완전한 페이지 컨텍스트를 받을 수 있습니다. 테스트 용도이거나 페이지가 완전히 렌더링되었다고 확신하는 경우에만 비활성화하세요.
</Warning>

### 리로드 후 처리

가이드 단계 중에 전체 페이지 리로드가 발생하는 경우 `afterReload` 옵션을 활성화하여 상태를 보존하세요:

```tsx theme={null}
stability: {
  afterReload: {
    enabled: true,          // 명시적으로 활성화하지 않으면 꺼져 있음
    pendingTTL: 30000,      // 대기 상태가 유효한 시간 (기본값: 30000ms)
    storageKey: 'my-app-moss-pending',  // 대기 상태를 저장하는 키 재정의
  },
}
```

| 필드           | 타입        | 기본값     | 설명                              |
| ------------ | --------- | ------- | ------------------------------- |
| `enabled`    | `boolean` | `false` | 전체 페이지 리로드를 거쳐 대기 중인 가이드 상태를 보존 |
| `pendingTTL` | `number`  | `30000` | 대기 마커가 폐기되기 전까지 유효한 시간(밀리초)     |
| `storageKey` | `string`  | SDK 기본값 | 대기 마커를 저장하는 데 사용하는 스토리지 키       |

***

## Shadow DOM

SDK는 모든 UI를 `document.body`에 첨부된 Shadow DOM 내부에 렌더링합니다. 이는 다음을 의미합니다:

* **여러분의 CSS가 Moss UI에 영향을 줄 수 없습니다** — 스타일이 완전히 캡슐화됨
* **Moss CSS가 여러분의 앱에 영향을 줄 수 없습니다** — 스타일 누출 없음
* **여러분의 앱에서 DOM 쿼리로 Moss 요소를 찾을 수 없습니다** — `document.querySelector`가 Shadow Root 내부의 요소를 매칭하지 않음

<Tip>
  자동화된 테스트 도구(Cypress, Playwright 등)를 사용하는 경우 Moss 요소와 상호 작용하려면 Shadow DOM을 관통해야 합니다. `#moss-shadow-host` 호스트 요소를 찾으세요(0.16 이하 SDK 버전에서는 `#clippy-shadow-host`).
</Tip>

***

## Content Security Policy (CSP)

애플리케이션에서 엄격한 CSP 헤더를 사용하는 경우 다음을 허용해야 할 수 있습니다:

| 디렉티브          | 값                      | 이유             |
| ------------- | ---------------------- | -------------- |
| `connect-src` | Moss 백엔드 URL           | API 요청         |
| `script-src`  | CDN URL (스크립트 태그 사용 시) | SDK 스크립트       |
| `style-src`   | `'unsafe-inline'`      | Shadow DOM 스타일 |
| `img-src`     | `blob:` `data:`        | 스크린샷 처리        |

CSP 헤더 예시:

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

***

## DOM 관찰 범위

기본적으로 SDK는 `document.body`의 변경을 관찰합니다. 페이지의 특정 부분으로 관찰을 제한하려면:

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

다음과 같은 경우에 유용합니다:

* 앱에 재캡처를 트리거하지 말아야 할 복잡한 사이드바나 헤더가 있는 경우
* 관련 없는 DOM 변경으로 인한 노이즈를 줄이고 싶은 경우
* 어시스턴트가 페이지의 특정 섹션만 도와야 하는 경우

***

## 네이티브 모달 다이얼로그

애플리케이션이 네이티브 `<dialog>`를 `showModal()`로 열면, 브라우저는 다이얼로그
바깥의 모든 요소를 비활성(inert) 상태로 만듭니다. Moss 위젯도 예외가 아닙니다.
사용자는 어시스턴트를 클릭할 수 없고, 진행 중이던 가이드도 이어갈 수 없습니다.

애플리케이션에서 `showModal()`을 사용한다면, 위젯을 마운트한 요소를 넘겨 모달
다이얼로그 이스케이프를 설치하세요:

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

const uninstall = installModalDialogEscape(widgetMountElement);

// 나중에 위젯을 해제할 때:
uninstall();
```

모달 다이얼로그가 열려 있는 동안 SDK 호스트는 다이얼로그의 트리 안으로 임시
이동되며(그 위로 올라갑니다), 덕분에 위젯은 계속 조작 가능한 상태로 뷰포트 위치를
유지합니다. 다이얼로그가 닫히면 호스트는 원래 DOM 위치로 복원됩니다. 반환된 함수는
리스너를 제거하고 호스트를 복원합니다.

<Warning>
  통합에서 온전히 소유한 요소, 즉 Moss 위젯을 마운트한 컨테이너를 넘기세요.
  애플리케이션 프레임워크가 관리하는 노드는 절대 넘기지 마세요. 이스케이프는
  요소를 다른 위치로 옮기므로 프레임워크의 렌더링과 충돌합니다.
</Warning>

**제한 사항:**

* Shadow root 내부에 렌더링된 다이얼로그는 추적되지 않습니다.
* Popover API를 지원하지 않는 브라우저에서는 위젯이 조작 가능한 상태로 유지되지만,
  `overflow: hidden`이나 transform이 적용된 다이얼로그 안에서는 잘려 보일 수
  있습니다.

***

## 민감 콘텐츠 편집

### `data-moss-redact`를 이용한 선언적 편집

HTML 요소에 `data-moss-redact` 속성을 지정하면 Moss 백엔드로 전송되는 모든
데이터에서 해당 요소가 편집됩니다. 민감한 콘텐츠를 보호하는 가장 간단한 방법입니다.

```html theme={null}
<div data-moss-redact>
  <p>계좌 잔액: $12,340.56</p>
  <p>주민등록번호: 123-45-6789</p>
</div>
```

**편집되는 대상:**

* **텍스트 콘텐츠** — 백엔드로 전송되는 구조화된 컨텍스트에서 `[REDACTED]`로 대체
* **민감한 속성** — `value`, `placeholder`, `title`, `alt`, `aria-label`, `href`,
  `src`, `name`이 모두 편집됨
* **하위 요소** — 편집 대상 요소의 모든 하위 요소가 제외됨

편집은 표시된 요소와 그 상위 10단계까지의 조상에 적용되므로, 컨테이너 하나만
표시해 내부 전체를 편집할 수 있습니다.

```html theme={null}
<!-- 이 섹션 안의 모든 폼 필드가 편집됩니다 -->
<section data-moss-redact>
  <input type="text" placeholder="카드 번호" />
  <input type="text" placeholder="CVV" />
</section>
```

### 입력값 편집 (기본 활성화)

`input`, `textarea`, `select`, `contenteditable` 영역을 포함한 모든 입력 요소의
값은 기본적으로 편집됩니다. 사용자가 폼에 입력한 내용은 페이지 컨텍스트로 브라우저를
벗어나지 않습니다.

내용이 안전하고 어시스턴트에게 유용하다고 확인된 특정 필드(예: 검색창)를 대상에서
제외하려면 `data-moss-unredact`를 지정하세요:

```html theme={null}
<input type="search" data-moss-unredact placeholder="상품 검색…" />
```

입력값 편집을 완전히 비활성화하려면 `redactAllInputs: false`로 설정하세요:

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

<Warning>
  애플리케이션의 모든 폼을 검토하지 않았다면 `redactAllInputs`를 켜 둔 채로
  두세요. 비활성화하면 사용자가 입력한 폼 값이 Moss로 전송되는 페이지 컨텍스트에
  포함됩니다.
</Warning>

### CSS 선택자를 이용한 편집

애플리케이션이 이미 다른 도구를 위해 민감한 요소를 표시하고 있다면(예: FullStory의
`.fs-exclude` / `.fs-mask` 클래스), 모든 곳에 `data-moss-redact`를 추가하는 대신
기존 표시를 그대로 재사용하세요. `redactionSelectors`에 일치하는 요소는
`data-moss-redact`와 동일하게 편집됩니다:

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

`data-moss-redact`는 이 설정과 관계없이 항상 인식됩니다. 한 요소가 허용 규칙과 편집
규칙에 모두 해당하면 편집 규칙이 우선합니다.

### 편집이 적용되는 범위

편집은 컨텍스트로 전송되는 페이지 텍스트와 구조, 그리고 단계별 가이드 진행 중
기록되는 상호작용 리포트에 적용됩니다. 사용자가 입력하는 채팅 메시지, 스크린샷, 세션
기록, 화면 히스토리는 편집되지 않습니다. 해당 결과물이 여러분의 환경에서 허용되지
않는다면 각 기능을 개별적으로 비활성화하세요.

### 프로그래매틱 정리

더 복잡한 정리 로직이 필요하다면 `pageSanitizationScript`로 캡처 전에 복제된 DOM을
수정하세요:

```tsx theme={null}
<AgentProvider config={{
  // ...
  pageSanitizationScript: (doc) => {
    // 신용카드 필드 제거
    doc.querySelectorAll('[data-sensitive]').forEach(el => {
      el.textContent = '[편집됨]';
    });
    // 트래킹 픽셀 제거
    doc.querySelectorAll('img[width="1"]').forEach(el => el.remove());
  },
}}>
```

<Warning>
  두 방식 모두 AI가 보는 내용에만 영향을 줍니다. 실제 DOM은 변경되지 않으므로
  사용자에게는 아무런 변화가 보이지 않습니다.
</Warning>

***

## 문제 해결

### 어시스턴트 버튼이 나타나지 않음

1. 브라우저 콘솔에서 `[MossSDK]` 오류를 확인하세요
2. `applicationId`가 대시보드의 유효한 애플리케이션과 일치하는지 확인하세요
3. `apiUrl`이 브라우저에서 접근 가능한지 확인하세요
4. JWT 서명 키가 활성 상태이고 폐기되지 않았는지 확인하세요

### "Failed to fetch config" 오류

* SDK가 백엔드에 연결할 수 없습니다. `apiUrl` 및 네트워크/CORS 설정을 확인하세요.
* CSP를 사용하는 경우 `connect-src`에 백엔드 URL이 포함되어 있는지 확인하세요.

### 스크린샷이 빈 화면이거나 불완전함

* `screenshotMode`를 `'viewport'`로 전환해 보세요
* 페이지에 느린 애니메이션이 있는 경우 `stability.domMutations.quietPeriodMs`를 늘리세요
* iframe이나 교차 출처 콘텐츠가 캡처를 차단하고 있는지 확인하세요

### 호스트 애플리케이션과 스타일 충돌

이것은 발생하지 않아야 합니다 — SDK는 Shadow DOM 격리를 사용합니다. 충돌이 보이는 경우:

* 앱에서 `*` 또는 `body` 선택자에 `!important`를 사용하고 있는지 확인하세요
* JavaScript가 Shadow Root를 수정하고 있지 않은지 확인하세요

### 디버그 모드

문제 진단을 위해 상세 로깅을 활성화하세요:

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

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

안정성 레이어 타이밍, 네트워크 요청, 컨텍스트 캡처 세부 정보 및 API 응답이 브라우저 콘솔에 기록됩니다.
