# 속성 계약 (attributes)

> 이 파일은 코드에서 자동 생성됩니다. **직접 편집하지 마세요.**
> 재생성: `cd hosting-web && npx tsx scripts/gen-docs.ts`
> 이름 진실 = `@saeroon/web-sdk` · `@saeroon/shop-sdk` · form-auto-attach · sdk-auto-inject 소스.

새로온에 배포한 정적 사이트는 HTML 요소에 `data-*` 속성을 선언하는 것만으로 회원·게시판·댓글·쇼핑·폼이 붙습니다. `cli deploy` 가 관련 속성을 감지해 SDK 로더를 자동 주입합니다.

## 개요

| 구분 | 수 | 비고 |
|---|---|---|
| `data-saeroon-*` 속성(저자용) | 46 | 아래 기능별 표 |
| `data-saeroon-*` 내부 마커 | 13 | SDK/CLI 가 자동 관리 |
| `data-saeroon-*` 소스 총계 | 59 | 소스 추출 = 문서화(대조 PASS) |
| 컴패니언 `data-*` | 25 | data-saeroon-* 와 함께 쓰는 일반 속성 |
| 부트/로더 `data-*` | 5 | script 태그(대개 cli 가 자동 inject) |

## 부트 / 로더

SDK 로더 `<script>` 태그의 속성입니다. 대개 `cli deploy` 가 자동으로 심습니다.

| 속성 | 적용 요소 | 값·모드 | 필수 | 동작 |
|---|---|---|---|---|
| `data-site-id` | 회원 SDK 로더 &lt;script&gt; | 사이트 UUID | 필수 — 없거나 빈 값이면 SDK 초기화 중단(console.error) | 테넌트/사이트 식별. auth-hint localStorage 키(saeroon:auth-hint:&lt;siteId&gt;)의 기반. cli deploy 가 자동 inject. |
| `data-api-base` | 회원/쇼핑 SDK 로더 &lt;script&gt; | 절대 URL · 빈 문자열=동일 출처 · 부재=https://api.saeroon.com | 선택 | ApiClient 의 API base. 속성 부재(null)면 운영 기본값, 빈 문자열이면 same-origin relative. |
| `data-pk` | 회원 SDK 로더 &lt;script&gt; | publishable key (pk_live_…) | 조건부 — 없으면 회원 인증/쓰기 미동작(공개 read 일부만), console.warn | 테넌트 해석용 publishable key(비밀 아님, HTML 노출 허용). 매 요청 X-Saeroon-Pk 헤더로 전송. (구) data-api-key(sk_live_) 폐기. |
| `data-site-slug` | 쇼핑 SDK 로더 &lt;script&gt; | 사이트 slug | 필수 — 없으면 shop SDK 초기화 중단(console.error) | 공개 쇼핑 API 스코프(회원 SDK 의 siteId 아닌 siteSlug). cli deploy 가 자동 inject. |
| `data-provider` | 쇼핑 SDK 로더 &lt;script&gt; | 결제 provider (기본 toss) | 선택 — 부재 시 toss | 결제 provider 힌트. site-aware /client-config 로 승격 가능. |

## 회원 인증

### 인증 · 조건부 렌더 · 바인딩

| 속성 | 적용 요소 | 값·모드 | 필수 | 동작 |
|---|---|---|---|---|
| `data-saeroon-auth` | form(register/login/profile/password) · button·link(logout/withdraw/social) | register · login · profile · password · logout · withdraw · kakao · naver · google · apple | 조건부 — 인증 UI 요소에 필수 | form 은 submit 에서 값(mode) 분기: register→회원가입, login→로그인, profile→프로필 수정(name/phone/displayName/avatarUrl/bio), password→비밀번호 변경(currentPassword/newPassword). logout·withdraw 는 버튼 클릭. 값이 kakao/naver/google/apple 이면 클릭 시 소셜 로그인으로 이동. 폼 값은 name 속성 필드에서 읽는다. |
| `data-saeroon-if` | 아무 컨테이너 요소 | logged-in · logged-out · tier:&lt;A,B,…&gt; | 선택 | 로그인 상태/등급으로 el.style.display 토글. tier: 는 쉼표 분리 등급 중 하나라도 충족 시 표시. 등급 순서 Free&lt;Bronze&lt;Silver&lt;Gold&lt;Platinum(exact-case), 미상/오타 등급은 fail-closed(숨김). |
| `data-saeroon-bind` | 아무 요소 (input/textarea 는 value, 그 외 textContent) | user.&lt;field&gt; — field ∈ id·siteId·email·name·displayName·avatarUrl·role·tier | 선택 | 'user.' 로 시작하는 path 만 처리. 세션 회원의 해당 필드 값을 채운다. member 없으면 빈 문자열. |
| `data-saeroon-error` | form 내부 오류/상태 표시 요소 (없으면 SDK 가 생성) | 유무 (내용은 SDK 가 씀) | 선택 — 없으면 showError 가 &lt;div&gt; 를 만들어 append | 인증/글/댓글 폼의 오류·성공 메시지 대상. 성공 시 글자색을 초록(#080)으로 바꾼다. ?auth-error=&lt;code&gt; 로 복귀하면(소셜 실패) 사람말 메시지를 표기한다. |

```html
<form data-saeroon-auth="login"><input name="email"><input name="password" type="password"></form>
<div data-saeroon-if="tier:Gold,Platinum">등급 전용</div>
<span data-saeroon-bind="user.displayName"></span>
<div data-saeroon-error></div>
```

## 게시판 / 글

### 목록 · 상세 · 작성 · 소유자 액션 · 필드

| 속성 | 적용 요소 | 값·모드 | 필수 | 동작 |
|---|---|---|---|---|
| `data-saeroon-post-list` | 컨테이너 | 유무 (data-board-id 동반 의무) | 조건부 — 목록 컨테이너에 필수 | 게시판 글 목록. 내부에 &lt;template data-saeroon-post-item&gt; 가 있으면 그 마크업을 글마다 복제(디자인 보존), 없으면 generic 카드 렌더. data-page-size(기본 10)·data-detail-base·data-saeroon-item-class-cycle 를 읽는다. |
| `data-saeroon-post-item` | &lt;template&gt; (post-list 내부) | 유무 | 선택 — 디자인 보존 모드용 | 글 1건마다 복제되는 슬롯. 복제본 내부 [data-saeroon-field] 요소만 데이터로 채운다 → 디자인 손실 0. |
| `data-saeroon-post-detail` | 컨테이너 | 유무 | 선택 | 글 상세. post id 는 URL ?post=&lt;id&gt; 또는 data-post-id 로 해석. 내부 [data-saeroon-field] 채움, 조회 시 서버가 viewCount 증가. 못 찾으면 [data-saeroon-empty] 토글. |
| `data-saeroon-post-body` | post-detail 내부 본문 래퍼 | 유무 | 선택 | 상세 로드 성공 시 display 를 노출한다(기본 숨김 마크업 지원). 초기 빈 상태에서 본문 깜빡임 방지. |
| `data-saeroon-post-link` | post-item 템플릿 내부 링크 | 유무 | 선택 | href 에 "?post=&lt;id&gt;" 를 자동 부착. base = list[data-detail-base] 우선, 없으면 링크 기존 href. 요소가 없으면 출력 byte-동일(무영향). |
| `data-saeroon-post-create` | form | 유무 (data-board-id 동반 의무) | 조건부 — 글쓰기 폼에 필수 | 글 작성 폼. name="title"·name="content" 필드를 읽어 createPost 호출. data-visibility 로 공개범위 지정. 내부에 input[type=file][data-saeroon-post-image] 가 있으면 첨부 이미지를 R2 업로드 후 markdown 으로 본문에 삽입. |
| `data-saeroon-post-image` | input[type=file] (post-create 내부) | 유무 ([multiple] 허용) | 선택 — 운영자 이미지 첨부용 | 제출 전 선택 파일을 R2 업로드(uploadAsset) 후 ![](url) markdown 을 content 앞에 삽입, contentType 을 markdown 으로 승격. |
| `data-saeroon-post-actions` | 컨테이너 (상세 내부) | 유무 (기본 hidden 권장) | 선택 — 소유자 액션 UI | 상세 응답의 isMine === true 일 때만 hidden 을 제거해 노출한다(작성자/운영자만). isMine 부재(구 백엔드) = 유지(하위호환). |
| `data-saeroon-post-update` | form | 유무 | 선택 — 글 수정 폼 | submit 시 updatePost(title/content). 소유자(isMine)면 title/content 원문이 자동 프리필된다. 성공 후 상세 재조회 + saeroon:post-updated 이벤트. |
| `data-saeroon-post-delete` | button | 유무 | 선택 — 글 삭제 버튼 | confirm(data-confirm 기본 "이 글을 삭제할까요?") 후 deletePost. 성공 시 data-redirect 또는 현재 경로로 이동. |
| `data-saeroon-field` | post-list/detail/comment 스코프 내부 요소 | title · authorName · date · createdAt · content · viewCount · likeCount · commentCount · tag · thumbnail · image · thumbnailUrl · embed · depth · meta:&lt;key&gt; | 선택 | 스코프의 글/댓글 값을 안전 렌더한다(텍스트/이미지 src/배경). content 는 contentType=markdown 만 안전 마크다운(escape-first) 렌더, 그 외 textContent. embed 는 https src 만. meta:&lt;key&gt; 는 게시판별 자유 메타. |
| `data-saeroon-empty` | post-list/detail/comment-list 내부 빈상태 요소 | 유무 | 선택 | 목록이 비었거나 로드 실패 시 노출. data-keep-text 가 있으면 기본 문구를 덮어쓰지 않는다. |
| `data-saeroon-date-locale` | post-list/comment-list (날짜 필드 스코프) | BCP-47 locale (기본 ko-KR) | 선택 | date/createdAt 필드 표기 locale. 없으면 ko-KR(기존 출력과 동일). |
| `data-saeroon-date-format` | post-list/comment-list (날짜 필드 스코프) | long · (기본) | 선택 | long 이면 "YYYY년 M월 D일" 형식, 없으면 짧은 기본 형식. |
| `data-saeroon-item-class-cycle` | post-list | 공백 구분 클래스 목록 | 선택 | 복제본마다 클래스를 순환 적용(예: bento grid-span). 가변 글 수에서도 항목별 크기/위치 디자인을 보존한다. |
| `data-saeroon-like` | button | 유무 | 선택 | 좋아요 토글. 익명은 localStorage UUID 로 중복 방지. aria-pressed·is-liked 클래스 갱신, [data-saeroon-field="likeCount"] 숫자 갱신. |

```html
<div data-saeroon-post-list data-board-id="notice" data-page-size="10"></div>
<template data-saeroon-post-item><article><h3 data-saeroon-field="title"></h3></article></template>
<article data-saeroon-post-detail><h1 data-saeroon-field="title"></h1></article>
<div data-saeroon-post-body style="display:none"><div data-saeroon-field="content"></div></div>
<a data-saeroon-post-link href="/board/post/"><h3 data-saeroon-field="title"></h3></a>
<form data-saeroon-post-create data-board-id="inquiry" data-visibility="OwnerOnly"><input name="title"><textarea name="content"></textarea></form>
<input type="file" data-saeroon-post-image multiple accept="image/*">
<div data-saeroon-post-actions hidden><button data-saeroon-post-delete>삭제</button></div>
<form data-saeroon-post-update><input name="title"><textarea name="content"></textarea></form>
<button data-saeroon-post-delete data-confirm="이 글을 삭제할까요?">삭제</button>
<h3 data-saeroon-field="title"></h3>
<p data-saeroon-empty>아직 글이 없습니다.</p>
<time data-saeroon-field="date" data-saeroon-date-locale="ko-KR"></time>
<time data-saeroon-field="date" data-saeroon-date-format="long"></time>
<div data-saeroon-post-list data-saeroon-item-class-cycle="b1 b2 b3"></div>
<button data-saeroon-like>좋아요 <b data-saeroon-field="likeCount">0</b></button>
```

## 댓글

### 댓글 목록 · 작성 · 소유자 액션

| 속성 | 적용 요소 | 값·모드 | 필수 | 동작 |
|---|---|---|---|---|
| `data-saeroon-comment-list` | 컨테이너 | 유무 | 선택 | 댓글 목록(디자인 보존 template 모드). post id = ?post= 또는 data-post-id. 삭제/수정 버튼의 이벤트 위임 루트이기도 하다. data-page-size 기본 50. |
| `data-saeroon-comment-item` | &lt;template&gt; (comment-list 내부) | 유무 | 선택 | 댓글 1건마다 복제되는 슬롯. 대댓글(depth&gt;0)은 복제본에 data-comment-depth 가 붙어 스타일 훅을 준다. |
| `data-saeroon-comment-create` | form | 유무 | 선택 | 댓글 작성 폼. name="content"(필수)·name="authorName"(익명 허용 보드)·name="parentId"(대댓글). 성공 시 목록 + 댓글 수 갱신. |
| `data-saeroon-comment-actions` | 컨테이너 (comment-item 내부) | 유무 (기본 hidden 권장) | 선택 — 소유자 액션 | 댓글 isMine === true 인 복제본만 hidden 제거. 어휘 요소 없는 복제본은 출력 무변화. |
| `data-saeroon-comment-edit` | button (comment-list 내부) | 유무 | 선택 | 클릭 시 같은 댓글의 [data-saeroon-comment-edit-form] hidden 을 토글(수정 폼 열기/닫기). 이벤트 위임. |
| `data-saeroon-comment-edit-form` | form 또는 래퍼 (comment-item 내부) | 유무 | 선택 | 댓글 수정 폼. 소유자면 textarea[name="content"] 에 원문이 프리필된다. submit 시 updateComment. |
| `data-saeroon-comment-delete` | button (comment-list 내부) | 유무 | 선택 | confirm(data-confirm 기본 "이 댓글을 삭제할까요?") 후 deleteComment. 성공 시 목록 갱신. 이벤트 위임. |

```html
<div data-saeroon-comment-list><template data-saeroon-comment-item>…</template></div>
<template data-saeroon-comment-item><b data-saeroon-field="authorName"></b><p data-saeroon-field="content"></p></template>
<form data-saeroon-comment-create><textarea name="content"></textarea></form>
<span data-saeroon-comment-actions hidden><button data-saeroon-comment-edit>수정</button></span>
<button data-saeroon-comment-edit>수정</button>
<form data-saeroon-comment-edit-form hidden><textarea name="content"></textarea></form>
<button data-saeroon-comment-delete data-confirm="이 댓글을 삭제할까요?">삭제</button>
```

## 쇼핑

### 장바구니 · 담기

| 속성 | 적용 요소 | 값·모드 | 필수 | 동작 |
|---|---|---|---|---|
| `data-saeroon-shop` | 아무 요소 | 유무 | 선택 — 쇼핑 SDK inject 트리거 | cli deploy 가 이 마커(또는 add-to-cart/cart-count)를 보면 @saeroon/shop-sdk 로더를 &lt;head&gt;에 자동 inject 한다. 런타임 바인더가 읽는 마커는 아니다(주입 트리거 전용). |
| `data-saeroon-add-to-cart` | button | 유무 (data-product-id 또는 data-product-slug 동반 의무) | 조건부 — 담기 버튼에 필수 | 클릭 시 서버 카트에 담는다(수량 = data-quantity 또는 data-quantity-from 선택자의 input 값, 기본 1). 진행 중 disabled+aria-busy. 성공 시 data-toast 메시지·data-redirect 이동. |
| `data-saeroon-cart-count` | 요소(배지) | 유무 | 선택 | 장바구니 총 수량 배지. 초기값 + saeroon:cart-change 이벤트마다 textContent 갱신. |
| `data-saeroon-shop-error` | 요소 | 유무 (내용은 SDK 가 씀) | 선택 | 담기 실패 시 오류 메시지 대상. 요소가 없으면 saeroon:toast 이벤트로 대체 표면화. |

```html
<body data-saeroon-shop>
<button data-saeroon-add-to-cart data-product-id="{id}" data-quantity="1">담기</button>
<span data-saeroon-cart-count>0</span>
<div data-saeroon-shop-error></div>
```

## 예약

### data-saeroon-booking (슬롯 예약 · Toss 결제)

| 속성 | 적용 요소 | 값·모드 | 필수 | 동작 |
|---|---|---|---|---|
| `data-saeroon-booking` | 예약 위젯 루트 컨테이너 | data-mode="appointment"(기본·회차 슬롯) \| "stay"(숙박 날짜범위) \| "stay-manage"(숙박 조회/취소) · data-service="&lt;id&gt;"(appointment 단일서비스 락) · data-room-type="&lt;id\|slug&gt;"(stay 단일룸 락) · data-toss-client-key="&lt;pk&gt;"(생략 시 플랫폼 client-config 런타임 조달) | 선택 — booking SDK inject 트리거 + 위젯 루트 | cli deploy 가 이 마커를 보면 web-sdk 로더 + data-site-slug(공개 booking/stay API=slug 스코프)를 주입한다. [appointment] 바인더가 루트 안에서 명명 영역을 채운다: [data-booking-services]+&lt;template data-booking-service&gt;([data-booking-field=name\|price\|description\|duration]) → [data-booking-cal-grid]/[data-booking-cal-prev\|next]/[data-booking-cal-title] 월달력 → [data-booking-slots]+&lt;template data-booking-slot&gt; → form[data-booking-form](name/phone/email/partySize/memo) → Toss v2 → [data-booking-result]+[data-booking-result-status\|number\|message]. 오류=[data-booking-error]. 익명(guest-token)·서버 결제 확정 포함. [stay] 반열림 [체크인,체크아웃) 날짜범위·룸타입 재고·박별 합산·전액/보증금: [data-stay-rooms]+&lt;template data-stay-room&gt;([data-stay-field=name\|description\|rate\|capacity\|min-stay]) → [data-stay-cal-grid]/[data-stay-cal-prev\|next]/[data-stay-cal-title] 범위달력(셀 is-open/is-off/is-ci/is-co/is-in-range) → &lt;select data-stay-guests&gt; → [data-stay-quote]([data-stay-quote-rows]+&lt;template data-stay-night&gt;([data-stay-field=date\|rate\|label])·[data-stay-quote-room-total\|extra-total\|addons-total\|total\|paynow\|remain])·[data-stay-mode-toggle](보증금 허용 시 바인더 렌더) → form[data-stay-form](name/phone/email) → hold(TTL 재고 점유) → Toss v2 → [data-stay-result]+[data-stay-result-status\|number\|message]. 선택 표시=[data-stay-selected-room\|range]·오류=[data-stay-error]. [stay-manage] 예약 조회/취소(?bn=&amp;gid= 또는 예약 브라우저 localStorage 복원): [data-stay-manage-status\|number\|range\|guests\|total\|paid\|refund\|message] → [data-stay-cancel] 클릭 시 취소정책 스냅샷 환불 미리보기 [data-stay-cancel-preview]([data-stay-cancel-refund\|fee\|rate\|reason]) → [data-stay-cancel-confirm]. 조회/취소 토큰(gid)은 URL 미노출(localStorage) — 예약한 브라우저에서만 자동 복원. |

```html
<div data-saeroon-booking data-mode="stay">…</div> / <div data-saeroon-booking data-mode="stay-manage">…</div>
```

## 폼 자동 연결

### data-saeroon-form (opt-in)

| 속성 | 적용 요소 | 값·모드 | 필수 | 동작 |
|---|---|---|---|---|
| `data-saeroon-form` | &lt;form&gt; | form slug | 조건부 — 자동 연결할 폼에 필수(opt-in) | cli deploy 가 이 폼을 감지 → 필드 자동 추출 → saeroon-api 폼 upsert → HTML 변환(속성 제거·id/action/method 설정·Turnstile 위젯·submit JS 삽입). 값이 곧 saeroon-api 폼 slug. |
| `data-saeroon-name` | &lt;form&gt; | 폼 이름 | 선택 | form 의 name 속성이 없을 때 폼 이름(관리자 UI 표기)으로 쓰인다. |
| `data-saeroon-include` | hidden input (data-saeroon-form 내부) | "true" | 선택 | hidden 입력은 기본 필드 추출에서 제외된다. "true" 를 박으면 명시적으로 포함한다. |

```html
<form data-saeroon-form="contact"><input name="email"></form>
<form data-saeroon-form="contact" data-saeroon-name="문의하기"></form>
<input type="hidden" name="source" value="landing" data-saeroon-include="true">
```

## 컴패니언 속성

`data-saeroon-*` 는 아니지만 위 기능과 함께 쓰는 일반 `data-*` 속성입니다.

| 속성 | 적용 요소 | 값·모드 | 필수 | 동작 |
|---|---|---|---|---|
| `data-saeroon-ct` | data-saeroon-editable 요소 | ContentType 이름 | 필수 — 편집 영역 네 짝의 하나 | 그 편집 값이 저장될 ContentType. 정적 사이트 인라인 편집은 예약 CT(page/site)를 쓴다. |
| `data-saeroon-entry` | data-saeroon-editable 요소 | 엔트리 키 | 필수 — 편집 영역 네 짝의 하나 | 그 편집 값이 저장될 엔트리. 보통 페이지 키(home·about 등)를 쓴다. |
| `data-catalog` | catalog-list · holding-list · holding-toggle · group-mark | 카탈로그 key | 필수 — 없으면 해당 요소는 no-op | 어느 카탈로그인지 지정. 운영자가 CLI `catalog apply` 로 만든 key 를 그대로 쓴다. |
| `data-policy` | group-list · group-create | 그룹 정책 key | 필수 — 없으면 해당 요소는 no-op | 어느 그룹 정책인지 지정. 운영자가 CLI `group apply` 로 만든 key 를 그대로 쓴다. |
| `data-group-id` | group-apply · group-members · group-mark | 그룹 id (GUID) | 필수 — 없으면 해당 요소는 no-op | 대상 그룹. group-list 가 그린 복제본에서 읽거나 URL 쿼리로 넘긴 값을 쓴다. |
| `data-item-id` | holding-toggle · group-mark | 카탈로그 항목 id (GUID) | 조건부 — holding-toggle 은 필수, group-mark 는 선택 | 대상 항목. catalog-list 가 그린 복제본에 이 속성이 자동으로 붙는다. |
| `data-member-id` | group-mark | 대상 회원 id (GUID) | 필수 — 없으면 해당 요소는 no-op | 표식을 받을 회원. group-members 명부에서 읽는다(자기 자신이 아니라 남에게 찍는 표식이다). |
| `data-mark-key` | group-mark | 표식 종류 문자열 | 선택 | 표식의 종류를 사이트가 정하는 어휘로 남긴다(담당 배정·확인 완료 등). 서버는 값을 해석하지 않고 저장한다. |
| `data-invite-code` | group-apply | 초대 코드 | 조건부 — 정책의 requireInviteWhileForming 이 켜져 있으면 결성 중 가입에 필요 | 초대 코드를 실어 가입을 신청한다. |
| `data-role-key` | group-apply | 직위 key | 선택 | 희망 직위를 지정해 신청한다. 미지정 시 정책의 isDefault 직위를 받는다. |
| `data-board-id` | post-list · post-create | 게시판 id | 필수(둘 중 하나) — data-board(slug)를 쓰면 cli 가 변환 | 어느 게시판인지 지정. 없으면 해당 요소는 console.warn 후 no-op. |
| `data-board` | post-list · post-create | 게시판 slug | 선택(대안) — cli deploy 가 data-board-id 로 해석 | board slug 를 배포 시 실제 id 로 변환(data-board → data-board-id). 미해석 slug 는 경고. |
| `data-page-size` | post-list(기본 10) · comment-list(기본 50) | 정수 | 선택 | 한 번에 불러올 글/댓글 수. |
| `data-visibility` | post-create | Public · MembersOnly · OwnerOnly | 선택 | 글 공개 범위. OwnerOnly = 작성자/운영자만 조회(비공개 문의 등). |
| `data-detail-base` | post-list | URL 경로 base | 선택 | 목록→상세 링크 base. data-saeroon-post-link 의 href 접두로 쓰인다. |
| `data-post-id` | post-detail · post-actions · post-update · post-delete | 글 id | 선택 — 없으면 URL ?post= 사용 | post id 를 명시. 우선순위: data-post-id &gt; ?post= 쿼리. |
| `data-confirm` | auth=withdraw · post-delete · comment-delete | 확인 메시지 | 선택 — 부재 시 기본 문구 | 삭제/탈퇴 전 confirm() 대화상자 문구. |
| `data-redirect` | auth form/logout/withdraw · post-delete · add-to-cart | URL 경로 | 선택 | 동작 성공 후 이동할 경로(logout/withdraw 는 기본 "/"). |
| `data-return` | auth=kakao/naver/google/apple (소셜) | 상대 또는 절대 URL | 선택 | 소셜 로그인 후 돌아올 경로. 현재 위치 기준 절대 https URL 로 만들어 전달한다. |
| `data-keep-text` | [data-saeroon-empty] | 유무 | 선택 | 빈 상태 요소의 기본 문구를 SDK 가 덮어쓰지 않게 보존한다. |
| `data-product-id` | add-to-cart | 상품 GUID | 필수(둘 중 하나) | 담을 상품(실 id). |
| `data-product-slug` | add-to-cart | 상품 slug | 선택(대안) — slug→id 조회 | slug 만 아는 상세 페이지용. 서버에서 상품을 조회해 실 id 로 담는다. |
| `data-quantity` | add-to-cart | 정수(&gt;0) | 선택 — 기본 1 | 담을 수량(직접 지정). |
| `data-quantity-from` | add-to-cart | CSS 선택자 | 선택 | 수량을 읽을 input 선택자. data-quantity 없을 때 사용. |
| `data-toast` | add-to-cart | 성공 문구 | 선택 | 담기 성공 시 saeroon:toast 이벤트로 표면화할 메시지. |

## 내부 / 자동 관리 마커

아래 속성은 **저자가 직접 쓰지 않습니다.** SDK 런타임 또는 `cli deploy` 가 자동으로 부착·관리합니다. 참고용으로만 기재합니다.

| 속성 | 적용 요소 | 값·모드 | 필수 | 동작 |
|---|---|---|---|---|
| `data-saeroon-auth-hint` | &lt;html&gt; (cli 가 &lt;head&gt; 인라인 리더로 삽입) | 유무 | 내부 — 저자 미작성 | 코어 로드 전 첫 페인트 동안 잘못된 data-saeroon-if 브랜치를 숨겨 로그인 카드 플래시를 막는 pre-paint cloak 짝. 인증 정합 후 &lt;html&gt;.saeroon-auth-ready 로 비활성화. |
| `data-saeroon-post-rendered` | post-item 복제본 | 유무 | 내부 — 런타임 마커 | 렌더된 글 복제본을 표시. 재렌더 시 이전 복제본만 교체(정적 마크업/템플릿 보존)하는 데 쓰인다. |
| `data-saeroon-comment-rendered` | comment-item 복제본 | 유무 | 내부 — 런타임 마커 | 렌더된 댓글 복제본 표시. 재렌더 시 이전 복제본만 교체. |
| `data-saeroon-sdk-injected` | 회원 SDK &lt;script&gt; (cli 삽입) | 유무 | 내부 — 멱등 마커 | cli deploy 가 회원 SDK 로더를 &lt;head&gt;에 1회만 넣도록 하는 멱등 표시. |
| `data-saeroon-shop-injected` | 쇼핑 SDK &lt;script&gt; (cli 삽입) | 유무 | 내부 — 멱등 마커 | cli deploy 가 쇼핑 SDK 로더를 &lt;head&gt;에 1회만 넣도록 하는 멱등 표시. |
| `data-saeroon-turnstile` | Turnstile 프록시 &lt;iframe&gt; (cli 삽입) | 유무 | 내부 — 저자 미작성 | form auto-attach 가 삽입하는 Cloudflare Turnstile 프록시 iframe 마커. 토큰을 hidden input 에 채운다. |
| `data-saeroon-catalog-rendered` | 카탈로그 항목 복제본 | 유무 | 내부 — 런타임 마커 | 렌더된 카탈로그 항목 복제본 표시. 재렌더 시 이전 복제본만 교체(정적 마크업/템플릿 보존). |
| `data-saeroon-holding-rendered` | 보유 항목 복제본 | 유무 | 내부 — 런타임 마커 | 렌더된 보유 복제본 표시. 토글 후 같은 카탈로그를 보는 목록만 다시 그릴 때 교체 대상이 된다. |
| `data-saeroon-group-rendered` | 그룹 목록 복제본 | 유무 | 내부 — 런타임 마커 | 렌더된 그룹 복제본 표시. 재렌더 시 이전 복제본만 교체. |
| `data-saeroon-group-member-rendered` | 그룹원 명부 복제본 | 유무 | 내부 — 런타임 마커 | 렌더된 그룹원 복제본 표시. 재렌더 시 이전 복제본만 교체. |
| `data-saeroon-held` | holding-toggle 버튼 | "true" \| "false" | 내부 — 런타임 상태 | 토글 직후 그 항목의 보유 여부를 버튼에 남긴다. 사이트 CSS 가 이 값으로 눌린 모양을 표현할 수 있다. |
| `data-saeroon-busy` | 요청 중인 버튼 | 유무 | 내부 — 런타임 상태 | 요청이 도는 동안 부착되고 끝나면 제거된다(버튼이면 disabled 도 함께). 중복 클릭 방지 + CSS 로딩 표시 훅. |
| `data-saeroon-editing` | &lt;html&gt; (편집 모드 진입 시) | 유무 | 내부 — 런타임 플래그 | 소유자가 ?saeroon-edit 로 편집 모드에 들어갔음을 표시. 오버레이 CSS 가 이 플래그로만 편집 UI 를 켠다. |

## 이벤트 (window)

| 이벤트 | 발생 | 소스 |
|---|---|---|
| `saeroon:cart-change` | 카트 변경 시(담기/수정/삭제) — detail.cart | packages/shop-sdk/src/binders.ts:4 |
| `saeroon:shop-ready` | 쇼핑 SDK 준비 완료 — detail.shop (window.saeroonShop) | packages/shop-sdk/src/index.ts:77 |
| `saeroon:toast` | 담기 성공/실패 토스트 — detail.message | packages/shop-sdk/src/binders.ts:99 |
| `saeroon:post-updated` | 글 수정 성공(폼 닫기용) | packages/web-sdk/src/binders.ts:238 |
