@roottale/cms-renderer-next 0.41.0 → 0.41.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/templates/section-locks.ts","../src/templates/publish-gate.ts","../src/templates/validate-sections.ts"],"sourcesContent":["export interface SectionLock {\n move?: boolean;\n remove?: boolean;\n structure?: boolean;\n}\n\ninterface TiptapNodeLike {\n type?: string;\n attrs?: Record<string, unknown>;\n}\n\ninterface SectionNodeSnapshot {\n id: string;\n index: number;\n sectionType: string;\n attrs: Record<string, unknown>;\n props: unknown;\n lock: SectionLock;\n}\n\nexport type SectionLockViolationCode =\n | \"locked_section_inserted\"\n | \"locked_section_removed\"\n | \"locked_section_moved\"\n | \"locked_section_lock_changed\"\n | \"locked_section_structure_changed\";\n\nexport interface SectionLockViolation {\n code: SectionLockViolationCode;\n sectionId: string;\n sectionType: string;\n field: string;\n}\n\nconst CONTENT_PROP_PATHS: Readonly<\n Record<string, readonly (readonly (string | number)[])[]>\n> = {\n hero: [\n [\"eyebrow\"],\n [\"title\"],\n [\"description\"],\n [\"imageSrc\"],\n [\"imageAlt\"],\n [\"cta\", \"label\"],\n [\"cta\", \"href\"],\n ],\n \"rich-text\": [[\"heading\"], [\"body\"]],\n services: [\n [\"heading\"],\n [\"description\"],\n [\"items\", Number.NaN, \"title\"],\n [\"items\", Number.NaN, \"description\"],\n [\"items\", Number.NaN, \"href\"],\n ],\n faq: [\n [\"heading\"],\n [\"items\", Number.NaN, \"question\"],\n [\"items\", Number.NaN, \"answer\"],\n ],\n \"cta-banner\": [[\"title\"], [\"description\"], [\"cta\", \"label\"], [\"cta\", \"href\"]],\n \"blog-list\": [[\"heading\"], [\"emptyMessage\"]],\n \"lead-form\": [[\"heading\"], [\"description\"], [\"submitLabel\"]],\n trust: [\n [\"heading\"],\n [\"metrics\", Number.NaN, \"value\"],\n [\"metrics\", Number.NaN, \"unit\"],\n [\"metrics\", Number.NaN, \"label\"],\n [\"metrics\", Number.NaN, \"source\"],\n [\"credentials\", Number.NaN, \"name\"],\n [\"credentials\", Number.NaN, \"issuer\"],\n [\"credentials\", Number.NaN, \"kind\"],\n [\"credentials\", Number.NaN, \"image\"],\n [\"logos\", Number.NaN, \"src\"],\n [\"logos\", Number.NaN, \"alt\"],\n [\"logos\", Number.NaN, \"href\"],\n [\"disclaimer\"],\n ],\n contact: [\n [\"heading\"],\n [\"form\", \"enabled\"],\n [\"phone\"],\n [\"address\"],\n [\"hours\", Number.NaN, \"day\"],\n [\"hours\", Number.NaN, \"open\"],\n [\"hours\", Number.NaN, \"close\"],\n [\"hours\", Number.NaN, \"note\"],\n [\"mapEmbedUrl\"],\n ],\n};\n\nfunction readSectionLock(value: unknown): SectionLock {\n if (!value || typeof value !== \"object\") return {};\n const input = value as Record<string, unknown>;\n return {\n ...(typeof input.move === \"boolean\" ? { move: input.move } : {}),\n ...(typeof input.remove === \"boolean\" ? { remove: input.remove } : {}),\n ...(typeof input.structure === \"boolean\"\n ? { structure: input.structure }\n : {}),\n };\n}\n\nfunction isSectionLocked(lock: SectionLock): boolean {\n return lock.move === true || lock.remove === true || lock.structure === true;\n}\n\nfunction valueEquals(left: unknown, right: unknown): boolean {\n return JSON.stringify(left) === JSON.stringify(right);\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return Boolean(value) && typeof value === \"object\" && !Array.isArray(value);\n}\n\nfunction collectChangedPaths(\n before: unknown,\n after: unknown,\n prefix: readonly (string | number)[] = [],\n): Array<readonly (string | number)[]> {\n if (valueEquals(before, after)) return [];\n if (Array.isArray(before) && Array.isArray(after)) {\n if (before.length !== after.length) return [prefix];\n return before.flatMap((item, index) =>\n collectChangedPaths(item, after[index], [...prefix, index]),\n );\n }\n if (isRecord(before) && isRecord(after)) {\n const keys = new Set([...Object.keys(before), ...Object.keys(after)]);\n return [...keys].flatMap((key) =>\n collectChangedPaths(before[key], after[key], [...prefix, key]),\n );\n }\n return [prefix];\n}\n\nfunction contentPathMatches(\n pattern: readonly (string | number)[],\n path: readonly (string | number)[],\n): boolean {\n if (pattern.length !== path.length) return false;\n return pattern.every((segment, index) => {\n const actual = path[index];\n return Number.isNaN(segment)\n ? typeof actual === \"number\"\n : segment === actual;\n });\n}\n\nexport function isSectionContentPropPath(\n sectionType: string,\n path: readonly (string | number)[],\n): boolean {\n const patterns = CONTENT_PROP_PATHS[sectionType] ?? [];\n return patterns.some((pattern) => contentPathMatches(pattern, path));\n}\n\nfunction collectSectionSnapshots(doc: unknown): SectionNodeSnapshot[] {\n const content =\n doc && typeof doc === \"object\"\n ? (doc as { content?: unknown }).content\n : undefined;\n if (!Array.isArray(content)) return [];\n return content.flatMap((node, index) => {\n if (!node || typeof node !== \"object\") return [];\n const tiptapNode = node as TiptapNodeLike;\n if (tiptapNode.type !== \"section\") return [];\n const attrs = tiptapNode.attrs ?? {};\n const id = typeof attrs._id === \"string\" ? attrs._id : \"\";\n if (!id) return [];\n return [\n {\n id,\n index,\n sectionType:\n typeof attrs.sectionType === \"string\" ? attrs.sectionType : \"\",\n attrs,\n props: attrs.props ?? {},\n lock: readSectionLock(attrs.lock),\n },\n ];\n });\n}\n\nexport function findSectionLockViolations(\n beforeDoc: unknown,\n afterDoc: unknown,\n): SectionLockViolation[] {\n const before = collectSectionSnapshots(beforeDoc);\n const after = collectSectionSnapshots(afterDoc);\n const afterById = new Map(after.map((section) => [section.id, section]));\n const beforeById = new Map(before.map((section) => [section.id, section]));\n const violations: SectionLockViolation[] = [];\n\n for (const section of before) {\n const next = afterById.get(section.id);\n if (!next) {\n if (section.lock.remove === true) {\n violations.push({\n code: \"locked_section_removed\",\n sectionId: section.id,\n sectionType: section.sectionType,\n field: \"(node)\",\n });\n }\n continue;\n }\n\n if (section.lock.move === true && section.index !== next.index) {\n violations.push({\n code: \"locked_section_moved\",\n sectionId: section.id,\n sectionType: section.sectionType,\n field: \"(index)\",\n });\n }\n\n if (\n !valueEquals(section.lock, next.lock) &&\n (isSectionLocked(section.lock) || isSectionLocked(next.lock))\n ) {\n violations.push({\n code: \"locked_section_lock_changed\",\n sectionId: section.id,\n sectionType: section.sectionType,\n field: \"lock\",\n });\n }\n\n if (section.lock.structure !== true) continue;\n\n for (const field of [\"_id\", \"sectionType\", \"variant\", \"theme\"] as const) {\n if (!valueEquals(section.attrs[field], next.attrs[field])) {\n violations.push({\n code: \"locked_section_structure_changed\",\n sectionId: section.id,\n sectionType: section.sectionType,\n field,\n });\n }\n }\n\n for (const path of collectChangedPaths(section.props, next.props)) {\n if (!isSectionContentPropPath(section.sectionType, path)) {\n violations.push({\n code: \"locked_section_structure_changed\",\n sectionId: section.id,\n sectionType: section.sectionType,\n field: `props.${path.join(\".\") || \"(root)\"}`,\n });\n }\n }\n }\n\n for (const section of after) {\n if (beforeById.has(section.id) || !isSectionLocked(section.lock)) continue;\n violations.push({\n code: \"locked_section_inserted\",\n sectionId: section.id,\n sectionType: section.sectionType,\n field: \"(node)\",\n });\n }\n\n return violations;\n}\n\nexport function formatSectionLockViolations(\n violations: readonly SectionLockViolation[],\n): string {\n return violations\n .map((violation) => {\n const label = `${violation.sectionType || \"section\"} 섹션(${violation.sectionId})`;\n if (violation.code === \"locked_section_inserted\") return `${label} 추가`;\n if (violation.code === \"locked_section_removed\") return `${label} 삭제`;\n if (violation.code === \"locked_section_moved\") return `${label} 이동`;\n if (violation.code === \"locked_section_lock_changed\") {\n return `${label} 잠금 변경`;\n }\n return `${label} 구조 변경(${violation.field})`;\n })\n .join(\" / \");\n}\n","import {\n MEDICAL_LINT_RULESET,\n lintMedicalPage,\n type Severity,\n} from \"@roottale/audit-medical\";\n\nexport const PUBLISH_GATE_RULESET = \"publish-gate-2026-07-02\";\n\nexport type PublishGateError = {\n readonly code: string;\n readonly message: string;\n readonly ruleId?: string;\n readonly severity?: Severity;\n readonly snippet?: string;\n};\n\nexport type PublishGateRuleset = {\n readonly publish: typeof PUBLISH_GATE_RULESET;\n readonly medical?: typeof MEDICAL_LINT_RULESET;\n};\n\nexport type PublishGateResult = {\n readonly errors: readonly PublishGateError[];\n readonly ruleset: PublishGateRuleset;\n};\n\nexport type PublishGateDoc = {\n readonly title?: string | null;\n readonly excerpt?: string | null;\n readonly bodyJson: Record<string, unknown>;\n readonly metaJson?: Record<string, unknown> | null;\n};\n\n/**\n * h1 모델 (W6-1) — 문서 타입별로 h1 소유자가 다르다:\n * - `\"page\"`: 본문(hero 섹션 또는 heading level=1)이 유일 h1. 렌더러가 `doc.title`\n * 을 h1 으로 그리지 않으므로 body h1Labels.length !== 1 → 차단.\n * - `\"post\"`: 렌더러(`RootTaleBlogPost`)가 `post.title` 을 h1 으로 그린다 —\n * 본문에 h1 이 있으면 실렌더 h1=2 가 되므로 body h1Labels.length !== 0 → 차단.\n * `documentType` 은 fail-closed 로 필수(optional 금지) — 신규 호출부가 실수로\n * type 분기를 빼먹으면 컴파일 에러가 나야 한다.\n */\nexport type PublishGateOptions = {\n readonly vertical: string;\n readonly complianceFlags?: readonly string[];\n readonly documentType: \"page\" | \"post\";\n};\n\nexport type PublishOverrideInput = {\n readonly reason: string;\n readonly userId: string;\n readonly at: string;\n};\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === \"object\" && !Array.isArray(value);\n}\n\nfunction recordOrEmpty(value: unknown): Record<string, unknown> {\n return isRecord(value) ? value : {};\n}\n\nfunction stringOrEmpty(value: unknown): string {\n return typeof value === \"string\" ? value.trim() : \"\";\n}\n\nfunction collectText(value: unknown, out: string[]): void {\n if (typeof value === \"string\") {\n out.push(value);\n return;\n }\n if (Array.isArray(value)) {\n for (const item of value) collectText(item, out);\n return;\n }\n if (!isRecord(value)) return;\n if (typeof value.text === \"string\") out.push(value.text);\n for (const child of Object.values(value)) collectText(child, out);\n}\n\nfunction textFrom(value: unknown): string {\n const out: string[] = [];\n collectText(value, out);\n return out.join(\"\\n\");\n}\n\nfunction seoDescription(doc: PublishGateDoc): string {\n const meta = recordOrEmpty(doc.metaJson);\n const seo = recordOrEmpty(meta.seo);\n return stringOrEmpty(seo.description) || stringOrEmpty(doc.excerpt);\n}\n\nfunction collectH1Labels(doc: Record<string, unknown>): string[] {\n const labels: string[] = [];\n const walk = (node: unknown): void => {\n if (!isRecord(node)) return;\n const type = stringOrEmpty(node.type);\n if (type === \"heading\") {\n const attrs = recordOrEmpty(node.attrs);\n if (Number(attrs.level ?? 2) === 1) labels.push(textFrom(node));\n }\n if (type === \"section\") {\n const attrs = recordOrEmpty(node.attrs);\n if (attrs.sectionType === \"hero\") {\n const props = recordOrEmpty(attrs.props);\n const title = stringOrEmpty(props.title);\n if (title) labels.push(title);\n }\n }\n const content = node.content;\n if (Array.isArray(content)) {\n for (const child of content) walk(child);\n }\n };\n walk(doc);\n return labels;\n}\n\nexport function assertPublishable(\n doc: PublishGateDoc,\n options: PublishGateOptions,\n): PublishGateResult {\n const errors: PublishGateError[] = [];\n const ruleset: {\n publish: typeof PUBLISH_GATE_RULESET;\n medical?: typeof MEDICAL_LINT_RULESET;\n } = { publish: PUBLISH_GATE_RULESET };\n const h1Labels = collectH1Labels(doc.bodyJson);\n\n if (options.documentType === \"post\") {\n // post = 렌더러가 title 을 h1 으로 그린다 — 본문에 h1 이 있으면 실렌더 h1=2.\n if (h1Labels.length !== 0) {\n errors.push({\n code: \"SEO_H1_COUNT\",\n message: `post 는 제목이 h1 이며 본문에 h1 금지, 현재 ${h1Labels.length}개입니다.`,\n });\n }\n } else {\n // page = 본문(hero 또는 heading1)이 유일 h1 — 렌더러는 title 을 h1 으로 그리지 않는다.\n if (h1Labels.length !== 1) {\n errors.push({\n code: \"SEO_H1_COUNT\",\n message: `h1은 정확히 1개여야 합니다. 현재 ${h1Labels.length}개입니다.`,\n });\n }\n }\n if (!stringOrEmpty(doc.title)) {\n errors.push({\n code: \"SEO_TITLE_REQUIRED\",\n message: \"발행하려면 글 제목이 필요합니다.\",\n });\n }\n if (!seoDescription(doc)) {\n errors.push({\n code: \"SEO_META_DESCRIPTION_REQUIRED\",\n message: \"발행하려면 SEO meta description 또는 excerpt가 필요합니다.\",\n });\n }\n\n if (\n options.vertical === \"medical\" ||\n options.complianceFlags?.includes(\"medical-review\")\n ) {\n ruleset.medical = MEDICAL_LINT_RULESET;\n const lint = lintMedicalPage({\n title: doc.title ?? \"\",\n excerpt: doc.excerpt,\n contentJson: {\n type: \"doc\",\n content: [{ type: \"text\", text: textFrom(doc.bodyJson) }],\n },\n });\n for (const finding of lint.findings) {\n if (finding.severity !== \"critical\") continue;\n errors.push({\n code: \"MEDICAL_COMPLIANCE\",\n ruleId: finding.ruleId,\n severity: finding.severity,\n message: finding.message,\n snippet: finding.snippet,\n });\n }\n }\n\n return { errors, ruleset };\n}\n\nexport function writePublishGateMeta(\n metaJson: Record<string, unknown> | null | undefined,\n result: PublishGateResult,\n): Record<string, unknown> {\n const meta = { ...recordOrEmpty(metaJson) };\n const current = recordOrEmpty(meta.compliance);\n meta.compliance = {\n ...current,\n blocked: result.errors.length > 0,\n ruleset: result.ruleset,\n errors: result.errors,\n checkedAt: new Date().toISOString(),\n ...(result.errors.length === 0 ? { override: null } : {}),\n };\n return meta;\n}\n\nexport function writePublishOverrideMeta(\n metaJson: Record<string, unknown> | null | undefined,\n override: PublishOverrideInput,\n): Record<string, unknown> {\n const meta = { ...recordOrEmpty(metaJson) };\n const compliance = recordOrEmpty(meta.compliance);\n meta.compliance = {\n ...compliance,\n blocked: false,\n override,\n };\n return meta;\n}\n\nexport function isComplianceBlocked(\n metaJson: Record<string, unknown> | null | undefined,\n): boolean {\n const compliance = recordOrEmpty(recordOrEmpty(metaJson).compliance);\n return compliance.blocked === true && !hasPublishOverride(metaJson);\n}\n\nexport function hasPublishOverride(\n metaJson: Record<string, unknown> | null | undefined,\n): boolean {\n const compliance = recordOrEmpty(recordOrEmpty(metaJson).compliance);\n const override = recordOrEmpty(compliance.override);\n return (\n stringOrEmpty(override.reason).length > 0 &&\n stringOrEmpty(override.userId).length > 0\n );\n}\n","/**\n * `@roottale/cms-renderer-next/validate` — 섹션 노드 저장 검증 (ADR-0066 §5 / §2-A).\n *\n * 위치 근거(codex v4-R1 + W1-1 위임 결정):\n * - 검증은 **SectionSchema(정의 SSoT)** 로 하는데, 그 스키마는\n * `cms-renderer-next/templates/site-config.ts` 에 있다.\n * - 청사진(blueprint R1-4)은 \"cms-core 위치\"를 제안했으나, `cms-core → cms-renderer-next`\n * 는 **역방향 import(금지 — cms-core 가 base 패키지)**. 따라서 SectionSchema 를\n * 소비하는 검증기는 cms-renderer-next 안에 있어야 한다.\n * - 순수 zod(React/cms-client 무의존)이라 별도 진입점 `./validate` 로 노출해\n * admin `posts.ts`(\"use server\") 가 렌더러 번들 전체를 끌어오지 않고 import 한다.\n * - 조립 게이트(R3-a-3)도 같은 함수를 `@roottale/cms-renderer-next/validate` 로\n * 공유한다(이중 구현 금지).\n *\n * 섹션 = generic Tiptap 노드 `{ type:\"section\", attrs:{ sectionType, variant, theme, props } }`\n * (ADR-0055 §3-2). 검증은 `{ type: attrs.sectionType, variant, theme, props }` 를\n * SectionSchema 로 parse — 빌트인 type 은 스키마로 강제(union fall-through 차단),\n * 미지 type 은 CustomSection 으로 통과(렌더러 customSections 로 해석).\n */\nimport { SectionSchema } from \"./site-config.js\";\n\nexport {\n findSectionLockViolations,\n formatSectionLockViolations,\n isSectionContentPropPath,\n type SectionLock,\n type SectionLockViolation,\n type SectionLockViolationCode,\n} from \"./section-locks.js\";\n\nexport {\n PUBLISH_GATE_RULESET,\n assertPublishable,\n hasPublishOverride,\n isComplianceBlocked,\n writePublishGateMeta,\n writePublishOverrideMeta,\n type PublishGateDoc,\n type PublishGateError,\n type PublishGateOptions,\n type PublishGateResult,\n type PublishGateRuleset,\n type PublishOverrideInput,\n} from \"./publish-gate.js\";\n\n/** 검증 실패 1건. path = 문서 내 섹션 노드 위치(0-base), field = zod issue 경로. */\nexport interface SectionValidationIssue {\n /** 문서 최상위 content 배열에서 섹션 노드의 index. */\n index: number;\n sectionType: string;\n /** zod issue 경로 (예: \"props.title\"). */\n field: string;\n message: string;\n}\n\nexport interface SectionValidationResult {\n valid: boolean;\n issues: SectionValidationIssue[];\n}\n\ninterface TiptapNodeLike {\n type?: string;\n attrs?: Record<string, unknown>;\n content?: unknown;\n text?: unknown;\n}\n\n/** 문서 최상위 content 에서 섹션 노드를 index 와 함께 수집. */\nfunction collectSectionNodes(\n doc: unknown,\n): Array<{ index: number; node: TiptapNodeLike }> {\n const content =\n doc && typeof doc === \"object\"\n ? (doc as { content?: unknown }).content\n : undefined;\n if (!Array.isArray(content)) return [];\n const out: Array<{ index: number; node: TiptapNodeLike }> = [];\n content.forEach((node, index) => {\n if (node && typeof node === \"object\" && (node as TiptapNodeLike).type === \"section\") {\n out.push({ index, node: node as TiptapNodeLike });\n }\n });\n return out;\n}\n\n/**\n * 섹션 노드의 attrs → SectionSchema 입력 `{type, variant, theme, props}`.\n * sectionType 이 SectionSchema 의 `type` 이 된다.\n */\nfunction sectionInputFromAttrs(attrs: Record<string, unknown> | undefined): {\n type: string;\n variant?: unknown;\n theme?: unknown;\n props?: unknown;\n} {\n const a = attrs ?? {};\n return {\n type: typeof a.sectionType === \"string\" ? a.sectionType : \"\",\n variant: a.variant ?? undefined,\n theme: a.theme ?? undefined,\n props: a.props ?? {},\n };\n}\n\n/**\n * 문서 내 모든 섹션 노드를 SectionSchema 로 검증. 섹션 노드가 없으면 valid=true\n * (no-op). 하나라도 스키마 위반이면 valid=false + issue 목록.\n *\n * posts.ts(create/update) 가 이 결과를 보고 저장을 거부한다. rawHtml 생성 금지 —\n * 실패는 저장 거부일 뿐 노드를 변형하지 않는다.\n */\nexport function validateSectionNodes(doc: unknown): SectionValidationResult {\n const sections = collectSectionNodes(doc);\n const issues: SectionValidationIssue[] = [];\n\n for (const { index, node } of sections) {\n const input = sectionInputFromAttrs(node.attrs);\n const result = SectionSchema.safeParse(input);\n if (!result.success) {\n for (const issue of result.error.issues) {\n issues.push({\n index,\n sectionType: input.type || \"(미지정)\",\n field: issue.path.join(\".\") || \"(root)\",\n message: issue.message,\n });\n }\n }\n }\n\n return { valid: issues.length === 0, issues };\n}\n\n/** issue 목록을 사람이 읽는 한 줄 메시지로 (저장 거부 오류 문구용). */\nexport function formatSectionIssues(issues: SectionValidationIssue[]): string {\n return issues\n .map(\n (i) =>\n `${i.sectionType} 섹션(${i.index + 1}번째)의 ${i.field}: ${i.message}`,\n )\n .join(\" / \");\n}\n"],"mappings":"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","names":["isRecord"]}
1
+ {"version":3,"sources":["../src/templates/section-locks.ts","../../audit-medical/src/index.ts","../src/templates/publish-gate.ts","../src/templates/validate-sections.ts"],"sourcesContent":["export interface SectionLock {\n move?: boolean;\n remove?: boolean;\n structure?: boolean;\n}\n\ninterface TiptapNodeLike {\n type?: string;\n attrs?: Record<string, unknown>;\n}\n\ninterface SectionNodeSnapshot {\n id: string;\n index: number;\n sectionType: string;\n attrs: Record<string, unknown>;\n props: unknown;\n lock: SectionLock;\n}\n\nexport type SectionLockViolationCode =\n | \"locked_section_inserted\"\n | \"locked_section_removed\"\n | \"locked_section_moved\"\n | \"locked_section_lock_changed\"\n | \"locked_section_structure_changed\";\n\nexport interface SectionLockViolation {\n code: SectionLockViolationCode;\n sectionId: string;\n sectionType: string;\n field: string;\n}\n\nconst CONTENT_PROP_PATHS: Readonly<\n Record<string, readonly (readonly (string | number)[])[]>\n> = {\n hero: [\n [\"eyebrow\"],\n [\"title\"],\n [\"description\"],\n [\"imageSrc\"],\n [\"imageAlt\"],\n [\"cta\", \"label\"],\n [\"cta\", \"href\"],\n ],\n \"rich-text\": [[\"heading\"], [\"body\"]],\n services: [\n [\"heading\"],\n [\"description\"],\n [\"items\", Number.NaN, \"title\"],\n [\"items\", Number.NaN, \"description\"],\n [\"items\", Number.NaN, \"href\"],\n ],\n faq: [\n [\"heading\"],\n [\"items\", Number.NaN, \"question\"],\n [\"items\", Number.NaN, \"answer\"],\n ],\n \"cta-banner\": [[\"title\"], [\"description\"], [\"cta\", \"label\"], [\"cta\", \"href\"]],\n \"blog-list\": [[\"heading\"], [\"emptyMessage\"]],\n \"lead-form\": [[\"heading\"], [\"description\"], [\"submitLabel\"]],\n trust: [\n [\"heading\"],\n [\"metrics\", Number.NaN, \"value\"],\n [\"metrics\", Number.NaN, \"unit\"],\n [\"metrics\", Number.NaN, \"label\"],\n [\"metrics\", Number.NaN, \"source\"],\n [\"credentials\", Number.NaN, \"name\"],\n [\"credentials\", Number.NaN, \"issuer\"],\n [\"credentials\", Number.NaN, \"kind\"],\n [\"credentials\", Number.NaN, \"image\"],\n [\"logos\", Number.NaN, \"src\"],\n [\"logos\", Number.NaN, \"alt\"],\n [\"logos\", Number.NaN, \"href\"],\n [\"disclaimer\"],\n ],\n contact: [\n [\"heading\"],\n [\"form\", \"enabled\"],\n [\"phone\"],\n [\"address\"],\n [\"hours\", Number.NaN, \"day\"],\n [\"hours\", Number.NaN, \"open\"],\n [\"hours\", Number.NaN, \"close\"],\n [\"hours\", Number.NaN, \"note\"],\n [\"mapEmbedUrl\"],\n ],\n};\n\nfunction readSectionLock(value: unknown): SectionLock {\n if (!value || typeof value !== \"object\") return {};\n const input = value as Record<string, unknown>;\n return {\n ...(typeof input.move === \"boolean\" ? { move: input.move } : {}),\n ...(typeof input.remove === \"boolean\" ? { remove: input.remove } : {}),\n ...(typeof input.structure === \"boolean\"\n ? { structure: input.structure }\n : {}),\n };\n}\n\nfunction isSectionLocked(lock: SectionLock): boolean {\n return lock.move === true || lock.remove === true || lock.structure === true;\n}\n\nfunction valueEquals(left: unknown, right: unknown): boolean {\n return JSON.stringify(left) === JSON.stringify(right);\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return Boolean(value) && typeof value === \"object\" && !Array.isArray(value);\n}\n\nfunction collectChangedPaths(\n before: unknown,\n after: unknown,\n prefix: readonly (string | number)[] = [],\n): Array<readonly (string | number)[]> {\n if (valueEquals(before, after)) return [];\n if (Array.isArray(before) && Array.isArray(after)) {\n if (before.length !== after.length) return [prefix];\n return before.flatMap((item, index) =>\n collectChangedPaths(item, after[index], [...prefix, index]),\n );\n }\n if (isRecord(before) && isRecord(after)) {\n const keys = new Set([...Object.keys(before), ...Object.keys(after)]);\n return [...keys].flatMap((key) =>\n collectChangedPaths(before[key], after[key], [...prefix, key]),\n );\n }\n return [prefix];\n}\n\nfunction contentPathMatches(\n pattern: readonly (string | number)[],\n path: readonly (string | number)[],\n): boolean {\n if (pattern.length !== path.length) return false;\n return pattern.every((segment, index) => {\n const actual = path[index];\n return Number.isNaN(segment)\n ? typeof actual === \"number\"\n : segment === actual;\n });\n}\n\nexport function isSectionContentPropPath(\n sectionType: string,\n path: readonly (string | number)[],\n): boolean {\n const patterns = CONTENT_PROP_PATHS[sectionType] ?? [];\n return patterns.some((pattern) => contentPathMatches(pattern, path));\n}\n\nfunction collectSectionSnapshots(doc: unknown): SectionNodeSnapshot[] {\n const content =\n doc && typeof doc === \"object\"\n ? (doc as { content?: unknown }).content\n : undefined;\n if (!Array.isArray(content)) return [];\n return content.flatMap((node, index) => {\n if (!node || typeof node !== \"object\") return [];\n const tiptapNode = node as TiptapNodeLike;\n if (tiptapNode.type !== \"section\") return [];\n const attrs = tiptapNode.attrs ?? {};\n const id = typeof attrs._id === \"string\" ? attrs._id : \"\";\n if (!id) return [];\n return [\n {\n id,\n index,\n sectionType:\n typeof attrs.sectionType === \"string\" ? attrs.sectionType : \"\",\n attrs,\n props: attrs.props ?? {},\n lock: readSectionLock(attrs.lock),\n },\n ];\n });\n}\n\nexport function findSectionLockViolations(\n beforeDoc: unknown,\n afterDoc: unknown,\n): SectionLockViolation[] {\n const before = collectSectionSnapshots(beforeDoc);\n const after = collectSectionSnapshots(afterDoc);\n const afterById = new Map(after.map((section) => [section.id, section]));\n const beforeById = new Map(before.map((section) => [section.id, section]));\n const violations: SectionLockViolation[] = [];\n\n for (const section of before) {\n const next = afterById.get(section.id);\n if (!next) {\n if (section.lock.remove === true) {\n violations.push({\n code: \"locked_section_removed\",\n sectionId: section.id,\n sectionType: section.sectionType,\n field: \"(node)\",\n });\n }\n continue;\n }\n\n if (section.lock.move === true && section.index !== next.index) {\n violations.push({\n code: \"locked_section_moved\",\n sectionId: section.id,\n sectionType: section.sectionType,\n field: \"(index)\",\n });\n }\n\n if (\n !valueEquals(section.lock, next.lock) &&\n (isSectionLocked(section.lock) || isSectionLocked(next.lock))\n ) {\n violations.push({\n code: \"locked_section_lock_changed\",\n sectionId: section.id,\n sectionType: section.sectionType,\n field: \"lock\",\n });\n }\n\n if (section.lock.structure !== true) continue;\n\n for (const field of [\"_id\", \"sectionType\", \"variant\", \"theme\"] as const) {\n if (!valueEquals(section.attrs[field], next.attrs[field])) {\n violations.push({\n code: \"locked_section_structure_changed\",\n sectionId: section.id,\n sectionType: section.sectionType,\n field,\n });\n }\n }\n\n for (const path of collectChangedPaths(section.props, next.props)) {\n if (!isSectionContentPropPath(section.sectionType, path)) {\n violations.push({\n code: \"locked_section_structure_changed\",\n sectionId: section.id,\n sectionType: section.sectionType,\n field: `props.${path.join(\".\") || \"(root)\"}`,\n });\n }\n }\n }\n\n for (const section of after) {\n if (beforeById.has(section.id) || !isSectionLocked(section.lock)) continue;\n violations.push({\n code: \"locked_section_inserted\",\n sectionId: section.id,\n sectionType: section.sectionType,\n field: \"(node)\",\n });\n }\n\n return violations;\n}\n\nexport function formatSectionLockViolations(\n violations: readonly SectionLockViolation[],\n): string {\n return violations\n .map((violation) => {\n const label = `${violation.sectionType || \"section\"} 섹션(${violation.sectionId})`;\n if (violation.code === \"locked_section_inserted\") return `${label} 추가`;\n if (violation.code === \"locked_section_removed\") return `${label} 삭제`;\n if (violation.code === \"locked_section_moved\") return `${label} 이동`;\n if (violation.code === \"locked_section_lock_changed\") {\n return `${label} 잠금 변경`;\n }\n return `${label} 구조 변경(${violation.field})`;\n })\n .join(\" / \");\n}\n","/**\n * `@roottale/audit-medical` — 의료법 §56 의료광고 lint 룰 (Phase 1 골격).\n *\n * 본 모듈은 *spec 기반 초안* — RootTale 운영자가 외부 변호사 분기별 검토\n * cycle (ADR-0012) 로 확정 후 production 적용. 현재는 admin compliance\n * dashboard 에서 *경고* 수준으로 노출.\n *\n * Input: Tiptap JSON content (admin-tenant 의 pages.contentJson 과 동일 schema).\n * Output: lint findings (severity / rule id / message / position).\n *\n * Rule ID 컨벤션: `MED-<3자리>` (외부 변호사 검토 후 확정).\n *\n * Reference:\n * - 의료법 §56 의료광고의 금지\n * - 의료법 시행령 §23 의료광고의 금지 기준\n * - 보건복지부 의료광고 사전심의 가이드라인 (https://www.mohw.go.kr)\n *\n * **DISCLAIMER**: 본 룰셋은 일반 spec 기반 초안. 실제 의료광고 적합성 판단은\n * 자율심의기구 (의료광고심의위원회) 사전심의 + 자격 있는 법률 자문 의무.\n * 본 lint 통과 != 법령 준수.\n */\n\nexport type Severity = \"critical\" | \"warning\" | \"info\";\n\nexport interface MedicalLintFinding {\n ruleId: string;\n severity: Severity;\n message: string;\n /** 매칭된 텍스트 snippet (앞뒤 20자) */\n snippet?: string;\n}\n\nexport interface MedicalLintRule {\n id: string;\n severity: Severity;\n description: string;\n /** 본문 (plain text) 매칭. 발견 시 finding 반환 */\n check: (text: string) => MedicalLintFinding[];\n}\n\n/* ---------- helpers ---------- */\n\nfunction snippetFor(text: string, idx: number, term: string): string {\n const start = Math.max(0, idx - 20);\n const end = Math.min(text.length, idx + term.length + 20);\n return `…${text.slice(start, end)}…`;\n}\n\nfunction findAll(text: string, terms: readonly string[]): { term: string; idx: number }[] {\n const out: { term: string; idx: number }[] = [];\n for (const term of terms) {\n let from = 0;\n while (from <= text.length) {\n const idx = text.indexOf(term, from);\n if (idx === -1) break;\n out.push({ term, idx });\n from = idx + term.length;\n }\n }\n return out;\n}\n\nfunction ruleFromTerms(\n id: string,\n severity: Severity,\n description: string,\n terms: readonly string[],\n message: (term: string) => string,\n): MedicalLintRule {\n return {\n id,\n severity,\n description,\n check(text) {\n return findAll(text, terms).map(({ term, idx }) => ({\n ruleId: id,\n severity,\n message: message(term),\n snippet: snippetFor(text, idx, term),\n }));\n },\n };\n}\n\n/* ---------- rules (25개 — 의료법 §56 카테고리별) ---------- */\n\nconst ABSOLUTE_SUPERLATIVES = [\n \"최고\",\n \"최상\",\n \"최대\",\n \"유일한\",\n \"유일하게\",\n \"단 하나\",\n \"최초의\",\n \"독보적\",\n \"넘버원\",\n \"1위\",\n \"국내 1위\",\n \"세계 1위\",\n];\n\nconst GUARANTEE_TERMS = [\n \"100%\",\n \"100 %\",\n \"완전한\",\n \"완벽한\",\n \"확실한\",\n \"절대\",\n \"무조건\",\n \"반드시\",\n \"보장\",\n \"guarantee\",\n];\n\nconst COMPARISON_TERMS = [\n \"타 병원보다\",\n \"다른 병원보다\",\n \"타 의원보다\",\n \"타 의원과 비교\",\n \"다른 의사보다\",\n \"비교 시\",\n];\n\nconst RISK_OMISSION_TERMS = [\n \"부작용 없는\",\n \"부작용 없음\",\n \"안전한\",\n \"안심\",\n \"통증 없는\",\n \"통증 0\",\n \"위험 없는\",\n];\n\nconst CURE_PROMISE_TERMS = [\n \"완치\",\n \"치료 효과 보장\",\n \"재발 없는\",\n \"재발 0\",\n \"100% 치유\",\n \"기적의\",\n];\n\nconst PRICE_DISCOUNT_PROMOTION_TERMS = [\n \"이벤트가\",\n \"특별 할인\",\n \"할인 행사\",\n \"원 → \",\n \"원으로 할인\",\n \"파격가\",\n \"최저가\",\n];\n\nconst TESTIMONIAL_TERMS = [\"환자 후기\", \"환자 사례\", \"치료 사례\", \"전후 사진\"];\n\nconst UNAUTHORIZED_TITLE_TERMS = [\"전문병원\", \"지정병원\", \"최고의 의사\"];\n\nconst FOREIGN_PROMOTION_TERMS = [\"의료관광 전문\", \"외국인 환자 1위\"];\n\nexport const MEDICAL_RULES: readonly MedicalLintRule[] = [\n // MED-001~005: 절대표현 / 보장\n ruleFromTerms(\n \"MED-001\",\n \"critical\",\n \"절대 우위 표현 금지 (의료법 §56 ②3)\",\n ABSOLUTE_SUPERLATIVES,\n (term) => `\"${term}\" 같은 절대 우위 표현은 사용할 수 없습니다.`,\n ),\n ruleFromTerms(\n \"MED-002\",\n \"critical\",\n \"치료 효과 보장 표현 금지 (의료법 §56 ②2)\",\n GUARANTEE_TERMS,\n (term) => `\"${term}\" 같은 보장성 표현은 의료광고에 사용할 수 없습니다.`,\n ),\n ruleFromTerms(\n \"MED-003\",\n \"critical\",\n \"다른 의료기관 비교 광고 금지 (의료법 §56 ②6)\",\n COMPARISON_TERMS,\n (term) => `다른 의료기관과 비교하는 표현 \"${term}\" 은 금지됩니다.`,\n ),\n ruleFromTerms(\n \"MED-004\",\n \"critical\",\n \"부작용·위험 누락 표현 금지 (의료법 §56 ②1)\",\n RISK_OMISSION_TERMS,\n (term) => `\"${term}\" 은 부작용·위험을 누락한 표현으로 분류될 수 있습니다.`,\n ),\n ruleFromTerms(\n \"MED-005\",\n \"critical\",\n \"완치·치유 보장 표현 금지 (의료법 §56 ②7)\",\n CURE_PROMISE_TERMS,\n (term) => `\"${term}\" 같은 완치·치유 보장 표현은 금지됩니다.`,\n ),\n // MED-006~010: 가격·이벤트 광고\n ruleFromTerms(\n \"MED-006\",\n \"warning\",\n \"할인·이벤트 광고 표시 (의료법 §56 ②8 환자 유인)\",\n PRICE_DISCOUNT_PROMOTION_TERMS,\n (term) => `\"${term}\" — 환자 유인 광고로 분류될 수 있어 사전심의 필요.`,\n ),\n ruleFromTerms(\n \"MED-007\",\n \"warning\",\n \"후기·사례 광고 (의료법 §56 ②4 치료 경험담)\",\n TESTIMONIAL_TERMS,\n (term) => `\"${term}\" — 치료 경험담은 자율심의기구 사전심의 후만 게재 가능.`,\n ),\n ruleFromTerms(\n \"MED-008\",\n \"critical\",\n \"미인증 명칭 사용 금지 (의료법 §56 ②10)\",\n UNAUTHORIZED_TITLE_TERMS,\n (term) =>\n `\"${term}\" — 보건복지부 지정·평가 없이 사용 금지. 인증 시 인증번호 명시 의무.`,\n ),\n ruleFromTerms(\n \"MED-009\",\n \"warning\",\n \"의료관광 광고 (의료법 §56 ② 외국인 환자 유치)\",\n FOREIGN_PROMOTION_TERMS,\n (term) =>\n `\"${term}\" — 외국인 환자 유치 광고는 의료해외진출법 추가 검토 필요.`,\n ),\n {\n id: \"MED-010\",\n severity: \"warning\",\n description: \"전문의 자격 표시 시 진료과목 명시 필요\",\n check(text) {\n if (/전문의/.test(text) && !/[가-힣]+과 전문의/.test(text)) {\n return [\n {\n ruleId: \"MED-010\",\n severity: \"warning\",\n message:\n \"전문의 표시 시 진료과목 명시 권장 (예: '내과 전문의', '피부과 전문의').\",\n },\n ];\n }\n return [];\n },\n },\n // MED-011~015: 필수 표시 누락\n {\n id: \"MED-011\",\n severity: \"info\",\n description: \"의료기관명 표시 권장\",\n check(text) {\n if (text.length > 100 && !/(의원|병원|치과|한의원|클리닉)/.test(text)) {\n return [\n {\n ruleId: \"MED-011\",\n severity: \"info\",\n message:\n \"본문에 의료기관명 (의원/병원/치과/한의원/클리닉) 표기 권장.\",\n },\n ];\n }\n return [];\n },\n },\n {\n id: \"MED-012\",\n severity: \"info\",\n description: \"의료인 이름·자격 표시 권장\",\n check(text) {\n if (\n text.length > 200 &&\n !/(원장|대표원장|의사|박사)/.test(text)\n ) {\n return [\n {\n ruleId: \"MED-012\",\n severity: \"info\",\n message: \"의료인 이름·자격 (원장/대표원장/의사 등) 표시 권장.\",\n },\n ];\n }\n return [];\n },\n },\n {\n id: \"MED-013\",\n severity: \"info\",\n description: \"사전심의 의무 광고 — 심의번호 표시\",\n check(text) {\n const promotional = /(전후|개선|효과|시술)/.test(text);\n const hasReview = /심의[ ]?번호|심의필|사전심의/.test(text);\n if (promotional && !hasReview) {\n return [\n {\n ruleId: \"MED-013\",\n severity: \"info\",\n message:\n \"시술 효과·전후 사진 광고는 자율심의 후 심의번호 표시 의무 검토.\",\n },\n ];\n }\n return [];\n },\n },\n {\n id: \"MED-014\",\n severity: \"warning\",\n description: \"신의료기술 평가 통과 여부 표시\",\n check(text) {\n if (/신의료기술|첨단의료/.test(text) && !/평가[ ]?통과|승인/.test(text)) {\n return [\n {\n ruleId: \"MED-014\",\n severity: \"warning\",\n message:\n \"신의료기술·첨단의료 표현은 보건복지부 평가 통과 여부 명시 필요.\",\n },\n ];\n }\n return [];\n },\n },\n {\n id: \"MED-015\",\n severity: \"warning\",\n description: \"임상시험 단계 미통과 표현 주의\",\n check(text) {\n if (/임상시험|연구중/.test(text) && !/평가[ ]?완료|승인/.test(text)) {\n return [\n {\n ruleId: \"MED-015\",\n severity: \"warning\",\n message: \"임상시험 단계 표현은 미승인 효과 광고로 분류될 수 있음.\",\n },\n ];\n }\n return [];\n },\n },\n // MED-016~020: 광고 매체 별\n {\n id: \"MED-016\",\n severity: \"info\",\n description: \"외부 링크 의료 정보 인용 시 출처 표기\",\n check(text) {\n if (/(논문|연구|study)/i.test(text) && !/(출처|reference|참고문헌)/i.test(text)) {\n return [\n {\n ruleId: \"MED-016\",\n severity: \"info\",\n message:\n \"논문·연구 인용 시 출처 명시 권장 (의료법 §56 ②4 객관적 근거).\",\n },\n ];\n }\n return [];\n },\n },\n ruleFromTerms(\n \"MED-017\",\n \"info\",\n \"조회수·만족도 자랑 표현 주의\",\n [\"만족도 1위\", \"방문수 1위\", \"조회수 1위\"],\n (term) =>\n `\"${term}\" — 자율심의기구 사전심의 + 객관적 근거 (조사 출처) 명시 필요.`,\n ),\n ruleFromTerms(\n \"MED-018\",\n \"warning\",\n \"특수효과 영상 사용 시 표시\",\n [\"before & after\", \"before and after\"],\n () =>\n \"전후 사진/영상 사용 시 시술 조건 (촬영 조건·개인차) 명시 의무 검토.\",\n ),\n ruleFromTerms(\n \"MED-019\",\n \"warning\",\n \"보험·실손보장 광고 (의료법 §56 ②8 환자 유인)\",\n [\"실손 보장\", \"전액 환급\", \"보험 적용\"],\n (term) => `\"${term}\" — 환자 유인 표현으로 분류될 수 있음. 사전심의 필요.`,\n ),\n ruleFromTerms(\n \"MED-020\",\n \"warning\",\n \"외국 의료인 자격 표시\",\n [\"미국 의사\", \"일본 의사\", \"MD from\"],\n (term) =>\n `\"${term}\" — 외국 자격은 국내 면허 보유 여부 명시 의무 (의료법 §27).`,\n ),\n // MED-021~025: vertical 외 보강\n ruleFromTerms(\n \"MED-021\",\n \"info\",\n \"온라인 예약 안내\",\n [\"예약 가능\", \"온라인 예약\"],\n () => \"온라인 예약은 의료법 §56 환자 유인 광고 경계 — 사전심의 검토.\",\n ),\n ruleFromTerms(\n \"MED-022\",\n \"info\",\n \"교통 편의 표현\",\n [\"주차 무료\", \"발렛 파킹\", \"셔틀 운영\"],\n (term) =>\n `\"${term}\" — 진료 외 혜택 강조는 환자 유인 광고로 분류될 수 있음.`,\n ),\n {\n id: \"MED-023\",\n severity: \"info\",\n description: \"외국어 표현 — 한국어 병기 권장\",\n check(text) {\n const ratio = (text.match(/[A-Za-z]/g)?.length ?? 0) / Math.max(text.length, 1);\n if (ratio > 0.3) {\n return [\n {\n ruleId: \"MED-023\",\n severity: \"info\",\n message:\n \"영문 비율 30% 초과 — 한국어 병기 권장 (가독성 + 의료광고 심의 가이드).\",\n },\n ];\n }\n return [];\n },\n },\n ruleFromTerms(\n \"MED-024\",\n \"critical\",\n \"비급여 진료비 명시 누락 (의료법 §45)\",\n [\"비급여 진료비 별도 문의\"],\n () =>\n \"비급여 진료비는 항목·가격 명시 의무 (의료법 §45, 비급여 진료비 고지).\",\n ),\n ruleFromTerms(\n \"MED-025\",\n \"info\",\n \"원격진료·비대면 진료 표현\",\n [\"원격진료\", \"비대면 진료\", \"온라인 진료\"],\n (term) =>\n `\"${term}\" — 현행 의료법 한시적 허용 범위 확인 + 출처 명시 권장.`,\n ),\n];\n\nexport const MEDICAL_LINT_RULESET = \"medical-2026-06-13\";\n\n/* ---------- engine ---------- */\n\ninterface TiptapNode {\n type?: string;\n text?: string;\n content?: TiptapNode[];\n}\n\nfunction extractText(doc: unknown): string {\n const out: string[] = [];\n const walk = (node: unknown): void => {\n if (!node || typeof node !== \"object\") return;\n const n = node as TiptapNode;\n if (typeof n.text === \"string\") out.push(n.text);\n if (Array.isArray(n.content)) n.content.forEach(walk);\n };\n walk(doc);\n return out.join(\"\\n\");\n}\n\nexport interface LintInput {\n title: string;\n excerpt?: string | null;\n contentJson: Record<string, unknown>;\n}\n\nexport interface LintResult {\n findings: MedicalLintFinding[];\n /** severity 별 count */\n counts: Record<Severity, number>;\n}\n\nexport function lintMedicalPage(input: LintInput): LintResult {\n const text = [\n input.title,\n input.excerpt ?? \"\",\n extractText(input.contentJson),\n ].join(\"\\n\");\n\n const findings: MedicalLintFinding[] = [];\n for (const rule of MEDICAL_RULES) {\n findings.push(...rule.check(text));\n }\n const counts: Record<Severity, number> = {\n critical: 0,\n warning: 0,\n info: 0,\n };\n for (const f of findings) counts[f.severity] += 1;\n return { findings, counts };\n}\n","import {\n MEDICAL_LINT_RULESET,\n lintMedicalPage,\n type Severity,\n} from \"@roottale/audit-medical\";\n\nexport const PUBLISH_GATE_RULESET = \"publish-gate-2026-07-02\";\n\nexport type PublishGateError = {\n readonly code: string;\n readonly message: string;\n readonly ruleId?: string;\n readonly severity?: Severity;\n readonly snippet?: string;\n};\n\nexport type PublishGateRuleset = {\n readonly publish: typeof PUBLISH_GATE_RULESET;\n readonly medical?: typeof MEDICAL_LINT_RULESET;\n};\n\nexport type PublishGateResult = {\n readonly errors: readonly PublishGateError[];\n readonly ruleset: PublishGateRuleset;\n};\n\nexport type PublishGateDoc = {\n readonly title?: string | null;\n readonly excerpt?: string | null;\n readonly bodyJson: Record<string, unknown>;\n readonly metaJson?: Record<string, unknown> | null;\n};\n\n/**\n * h1 모델 (W6-1) — 문서 타입별로 h1 소유자가 다르다:\n * - `\"page\"`: 본문(hero 섹션 또는 heading level=1)이 유일 h1. 렌더러가 `doc.title`\n * 을 h1 으로 그리지 않으므로 body h1Labels.length !== 1 → 차단.\n * - `\"post\"`: 렌더러(`RootTaleBlogPost`)가 `post.title` 을 h1 으로 그린다 —\n * 본문에 h1 이 있으면 실렌더 h1=2 가 되므로 body h1Labels.length !== 0 → 차단.\n * `documentType` 은 fail-closed 로 필수(optional 금지) — 신규 호출부가 실수로\n * type 분기를 빼먹으면 컴파일 에러가 나야 한다.\n */\nexport type PublishGateOptions = {\n readonly vertical: string;\n readonly complianceFlags?: readonly string[];\n readonly documentType: \"page\" | \"post\";\n};\n\nexport type PublishOverrideInput = {\n readonly reason: string;\n readonly userId: string;\n readonly at: string;\n};\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === \"object\" && !Array.isArray(value);\n}\n\nfunction recordOrEmpty(value: unknown): Record<string, unknown> {\n return isRecord(value) ? value : {};\n}\n\nfunction stringOrEmpty(value: unknown): string {\n return typeof value === \"string\" ? value.trim() : \"\";\n}\n\n/** 섹션 props 안에서 카피가 아닌 기계 필드 — snippet/lint 수집 제외 (W6-6). */\nconst NON_COPY_PROP_KEYS = new Set([\"href\", \"image\", \"url\", \"src\", \"id\", \"_id\"]);\n\nfunction collectPropsCopy(value: unknown, out: string[]): void {\n if (typeof value === \"string\") {\n out.push(value);\n return;\n }\n if (Array.isArray(value)) {\n for (const item of value) collectPropsCopy(item, out);\n return;\n }\n if (!isRecord(value)) return;\n for (const [key, child] of Object.entries(value)) {\n if (NON_COPY_PROP_KEYS.has(key)) continue;\n collectPropsCopy(child, out);\n }\n}\n\n/**\n * Tiptap doc 에서 사람이 읽는 텍스트만 구조 기반으로 수집한다 (W6-6):\n * `text` 필드 + `content` 재귀 + 섹션 노드의 `attrs.props` 카피(기계 필드 제외).\n * 과거에는 Object.values 전체를 훑어 노드명(\"paragraph\" 등)·variant·href 가\n * lint snippet 에 섞여 나왔다 — 단, 섹션 props 카피는 의료 lint 대상이므로\n * audit-medical 의 extractText(text+content만)처럼 좁히면 게이트 약화 회귀.\n */\nfunction collectText(value: unknown, out: string[]): void {\n if (Array.isArray(value)) {\n for (const item of value) collectText(item, out);\n return;\n }\n if (!isRecord(value)) return;\n if (typeof value.text === \"string\") out.push(value.text);\n if (value.type === \"section\") {\n const attrs = recordOrEmpty(value.attrs);\n collectPropsCopy(attrs.props, out);\n }\n if (Array.isArray(value.content)) collectText(value.content, out);\n}\n\n/** @internal 테스트 전용 export — 수집 계약(노이즈 0·props 카피 포함) 검증용. */\nexport function textFrom(value: unknown): string {\n const out: string[] = [];\n collectText(value, out);\n return out.join(\"\\n\");\n}\n\nfunction seoDescription(doc: PublishGateDoc): string {\n const meta = recordOrEmpty(doc.metaJson);\n const seo = recordOrEmpty(meta.seo);\n return stringOrEmpty(seo.description) || stringOrEmpty(doc.excerpt);\n}\n\nfunction collectH1Labels(doc: Record<string, unknown>): string[] {\n const labels: string[] = [];\n const walk = (node: unknown): void => {\n if (!isRecord(node)) return;\n const type = stringOrEmpty(node.type);\n if (type === \"heading\") {\n const attrs = recordOrEmpty(node.attrs);\n if (Number(attrs.level ?? 2) === 1) labels.push(textFrom(node));\n }\n if (type === \"section\") {\n const attrs = recordOrEmpty(node.attrs);\n if (attrs.sectionType === \"hero\") {\n const props = recordOrEmpty(attrs.props);\n const title = stringOrEmpty(props.title);\n if (title) labels.push(title);\n }\n }\n const content = node.content;\n if (Array.isArray(content)) {\n for (const child of content) walk(child);\n }\n };\n walk(doc);\n return labels;\n}\n\nexport function assertPublishable(\n doc: PublishGateDoc,\n options: PublishGateOptions,\n): PublishGateResult {\n const errors: PublishGateError[] = [];\n const ruleset: {\n publish: typeof PUBLISH_GATE_RULESET;\n medical?: typeof MEDICAL_LINT_RULESET;\n } = { publish: PUBLISH_GATE_RULESET };\n const h1Labels = collectH1Labels(doc.bodyJson);\n\n if (options.documentType === \"post\") {\n // post = 렌더러가 title 을 h1 으로 그린다 — 본문에 h1 이 있으면 실렌더 h1=2.\n if (h1Labels.length !== 0) {\n errors.push({\n code: \"SEO_H1_COUNT\",\n message: `post 는 제목이 h1 이며 본문에 h1 금지, 현재 ${h1Labels.length}개입니다.`,\n });\n }\n } else {\n // page = 본문(hero 또는 heading1)이 유일 h1 — 렌더러는 title 을 h1 으로 그리지 않는다.\n if (h1Labels.length !== 1) {\n errors.push({\n code: \"SEO_H1_COUNT\",\n message: `h1은 정확히 1개여야 합니다. 현재 ${h1Labels.length}개입니다.`,\n });\n }\n }\n if (!stringOrEmpty(doc.title)) {\n errors.push({\n code: \"SEO_TITLE_REQUIRED\",\n message: \"발행하려면 글 제목이 필요합니다.\",\n });\n }\n if (!seoDescription(doc)) {\n errors.push({\n code: \"SEO_META_DESCRIPTION_REQUIRED\",\n message: \"발행하려면 SEO meta description 또는 excerpt가 필요합니다.\",\n });\n }\n\n if (\n options.vertical === \"medical\" ||\n options.complianceFlags?.includes(\"medical-review\")\n ) {\n ruleset.medical = MEDICAL_LINT_RULESET;\n const lint = lintMedicalPage({\n title: doc.title ?? \"\",\n excerpt: doc.excerpt,\n contentJson: {\n type: \"doc\",\n content: [{ type: \"text\", text: textFrom(doc.bodyJson) }],\n },\n });\n for (const finding of lint.findings) {\n if (finding.severity !== \"critical\") continue;\n errors.push({\n code: \"MEDICAL_COMPLIANCE\",\n ruleId: finding.ruleId,\n severity: finding.severity,\n message: finding.message,\n snippet: finding.snippet,\n });\n }\n }\n\n return { errors, ruleset };\n}\n\nexport function writePublishGateMeta(\n metaJson: Record<string, unknown> | null | undefined,\n result: PublishGateResult,\n): Record<string, unknown> {\n const meta = { ...recordOrEmpty(metaJson) };\n const current = recordOrEmpty(meta.compliance);\n meta.compliance = {\n ...current,\n blocked: result.errors.length > 0,\n ruleset: result.ruleset,\n errors: result.errors,\n checkedAt: new Date().toISOString(),\n ...(result.errors.length === 0 ? { override: null } : {}),\n };\n return meta;\n}\n\nexport function writePublishOverrideMeta(\n metaJson: Record<string, unknown> | null | undefined,\n override: PublishOverrideInput,\n): Record<string, unknown> {\n const meta = { ...recordOrEmpty(metaJson) };\n const compliance = recordOrEmpty(meta.compliance);\n meta.compliance = {\n ...compliance,\n blocked: false,\n override,\n };\n return meta;\n}\n\nexport function isComplianceBlocked(\n metaJson: Record<string, unknown> | null | undefined,\n): boolean {\n const compliance = recordOrEmpty(recordOrEmpty(metaJson).compliance);\n return compliance.blocked === true && !hasPublishOverride(metaJson);\n}\n\nexport function hasPublishOverride(\n metaJson: Record<string, unknown> | null | undefined,\n): boolean {\n const compliance = recordOrEmpty(recordOrEmpty(metaJson).compliance);\n const override = recordOrEmpty(compliance.override);\n return (\n stringOrEmpty(override.reason).length > 0 &&\n stringOrEmpty(override.userId).length > 0\n );\n}\n","/**\n * `@roottale/cms-renderer-next/validate` — 섹션 노드 저장 검증 (ADR-0066 §5 / §2-A).\n *\n * 위치 근거(codex v4-R1 + W1-1 위임 결정):\n * - 검증은 **SectionSchema(정의 SSoT)** 로 하는데, 그 스키마는\n * `cms-renderer-next/templates/site-config.ts` 에 있다.\n * - 청사진(blueprint R1-4)은 \"cms-core 위치\"를 제안했으나, `cms-core → cms-renderer-next`\n * 는 **역방향 import(금지 — cms-core 가 base 패키지)**. 따라서 SectionSchema 를\n * 소비하는 검증기는 cms-renderer-next 안에 있어야 한다.\n * - 순수 zod(React/cms-client 무의존)이라 별도 진입점 `./validate` 로 노출해\n * admin `posts.ts`(\"use server\") 가 렌더러 번들 전체를 끌어오지 않고 import 한다.\n * - 조립 게이트(R3-a-3)도 같은 함수를 `@roottale/cms-renderer-next/validate` 로\n * 공유한다(이중 구현 금지).\n *\n * 섹션 = generic Tiptap 노드 `{ type:\"section\", attrs:{ sectionType, variant, theme, props } }`\n * (ADR-0055 §3-2). 검증은 `{ type: attrs.sectionType, variant, theme, props }` 를\n * SectionSchema 로 parse — 빌트인 type 은 스키마로 강제(union fall-through 차단),\n * 미지 type 은 CustomSection 으로 통과(렌더러 customSections 로 해석).\n */\nimport { SectionSchema } from \"./site-config.js\";\n\nexport {\n findSectionLockViolations,\n formatSectionLockViolations,\n isSectionContentPropPath,\n type SectionLock,\n type SectionLockViolation,\n type SectionLockViolationCode,\n} from \"./section-locks.js\";\n\nexport {\n PUBLISH_GATE_RULESET,\n assertPublishable,\n hasPublishOverride,\n isComplianceBlocked,\n writePublishGateMeta,\n writePublishOverrideMeta,\n type PublishGateDoc,\n type PublishGateError,\n type PublishGateOptions,\n type PublishGateResult,\n type PublishGateRuleset,\n type PublishOverrideInput,\n} from \"./publish-gate.js\";\n\n/** 검증 실패 1건. path = 문서 내 섹션 노드 위치(0-base), field = zod issue 경로. */\nexport interface SectionValidationIssue {\n /** 문서 최상위 content 배열에서 섹션 노드의 index. */\n index: number;\n sectionType: string;\n /** zod issue 경로 (예: \"props.title\"). */\n field: string;\n message: string;\n}\n\nexport interface SectionValidationResult {\n valid: boolean;\n issues: SectionValidationIssue[];\n}\n\ninterface TiptapNodeLike {\n type?: string;\n attrs?: Record<string, unknown>;\n content?: unknown;\n text?: unknown;\n}\n\n/** 문서 최상위 content 에서 섹션 노드를 index 와 함께 수집. */\nfunction collectSectionNodes(\n doc: unknown,\n): Array<{ index: number; node: TiptapNodeLike }> {\n const content =\n doc && typeof doc === \"object\"\n ? (doc as { content?: unknown }).content\n : undefined;\n if (!Array.isArray(content)) return [];\n const out: Array<{ index: number; node: TiptapNodeLike }> = [];\n content.forEach((node, index) => {\n if (node && typeof node === \"object\" && (node as TiptapNodeLike).type === \"section\") {\n out.push({ index, node: node as TiptapNodeLike });\n }\n });\n return out;\n}\n\n/**\n * 섹션 노드의 attrs → SectionSchema 입력 `{type, variant, theme, props}`.\n * sectionType 이 SectionSchema 의 `type` 이 된다.\n */\nfunction sectionInputFromAttrs(attrs: Record<string, unknown> | undefined): {\n type: string;\n variant?: unknown;\n theme?: unknown;\n props?: unknown;\n} {\n const a = attrs ?? {};\n return {\n type: typeof a.sectionType === \"string\" ? a.sectionType : \"\",\n variant: a.variant ?? undefined,\n theme: a.theme ?? undefined,\n props: a.props ?? {},\n };\n}\n\n/**\n * 문서 내 모든 섹션 노드를 SectionSchema 로 검증. 섹션 노드가 없으면 valid=true\n * (no-op). 하나라도 스키마 위반이면 valid=false + issue 목록.\n *\n * posts.ts(create/update) 가 이 결과를 보고 저장을 거부한다. rawHtml 생성 금지 —\n * 실패는 저장 거부일 뿐 노드를 변형하지 않는다.\n */\nexport function validateSectionNodes(doc: unknown): SectionValidationResult {\n const sections = collectSectionNodes(doc);\n const issues: SectionValidationIssue[] = [];\n\n for (const { index, node } of sections) {\n const input = sectionInputFromAttrs(node.attrs);\n const result = SectionSchema.safeParse(input);\n if (!result.success) {\n for (const issue of result.error.issues) {\n issues.push({\n index,\n sectionType: input.type || \"(미지정)\",\n field: issue.path.join(\".\") || \"(root)\",\n message: issue.message,\n });\n }\n }\n }\n\n return { valid: issues.length === 0, issues };\n}\n\n/** issue 목록을 사람이 읽는 한 줄 메시지로 (저장 거부 오류 문구용). */\nexport function formatSectionIssues(issues: SectionValidationIssue[]): string {\n return issues\n .map(\n (i) =>\n `${i.sectionType} 섹션(${i.index + 1}번째)의 ${i.field}: ${i.message}`,\n )\n .join(\" / \");\n}\n"],"mappings":"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","names":["isRecord"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@roottale/cms-renderer-next",
3
- "version": "0.41.0",
3
+ "version": "0.41.2",
4
4
  "type": "module",
5
5
  "description": "RootTale CMS public-render React/Next.js Server Components. SSR-only RSC components (RootTaleBlogList / RootTaleBlogPost / RootTaleLeadForm) for external customer sites.",
6
6
  "main": "./dist/index.js",
@@ -46,10 +46,10 @@
46
46
  "CHANGELOG.md"
47
47
  ],
48
48
  "dependencies": {
49
+ "embla-carousel-react": "^8.6.0",
49
50
  "zod": "^3.23.8",
50
- "@roottale/audit-medical": "^0.0.1",
51
- "@roottale/cms-core": "^0.41.0",
52
- "@roottale/cms-client": "^0.41.0"
51
+ "@roottale/cms-client": "^0.41.1",
52
+ "@roottale/cms-core": "^0.41.0"
53
53
  },
54
54
  "peerDependencies": {
55
55
  "react": "^19.0.0"
@@ -58,10 +58,12 @@
58
58
  "@types/node": "^22.0.0",
59
59
  "@types/react": "^19.0.0",
60
60
  "@types/react-dom": "^19.0.0",
61
+ "esbuild-plugin-preserve-directives": "^0.0.11",
61
62
  "react": "^19.0.0",
62
63
  "react-dom": "^19.0.0",
63
64
  "typescript": "^5.7.0",
64
- "vitest": "^2.1.0"
65
+ "vitest": "^2.1.0",
66
+ "@roottale/audit-medical": "^0.0.1"
65
67
  },
66
68
  "publishConfig": {
67
69
  "access": "public"
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/server.tsx","../src/LeadForm.tsx","../src/templates/sections.tsx","../src/render-section-node.tsx","../src/PostCard.tsx","../src/TableOfContents.tsx","../src/theme-css-vars.ts","../src/toc.ts","../src/FloatingCta.tsx","../src/block-to-react.tsx"],"sourcesContent":["/**\n * `@roottale/cms-renderer-next/server` — RootTale CMS public-render React Server Components.\n *\n * Drop into any RSC-capable framework (Next.js App Router, Astro server islands,\n * React Router server). Single-line import, SSR-only, no `'use client'` boundary.\n *\n * ```tsx\n * import { RootTaleBlogList } from \"@roottale/cms-renderer-next/server\";\n *\n * export default function BlogPage() {\n * return <RootTaleBlogList apiKey={process.env.ROOTTALE_API_KEY!} />;\n * }\n * ```\n *\n * Customer site MUST keep `apiKey` server-side. Browser bundle ships zero\n * RootTale credentials (ADR-0023 §5.1 #15).\n *\n * Design tokens auto-apply via the CSS import below. Customers can override\n * any class with their own CSS or Tailwind utilities — scoped selectors use\n * `:where()` to keep specificity at zero (ADR-0023 §5.1 #10).\n */\n\nimport type { CSSProperties, ReactElement, ReactNode } from \"react\";\nimport {\n type CmsPostContent,\n type CmsPostType,\n type CmsArchiveRelatedGroup,\n type RootTaleTheme,\n type RootTaleBlogSettings,\n DEFAULT_BLOG_SETTINGS,\n fetchBlogSettings,\n fetchPost,\n fetchPosts,\n fetchTheme,\n resolvePostDisplay,\n selectRelatedPosts,\n} from \"@roottale/cms-client/server\";\n// CSS는 customer 가 root layout 에서 명시적으로 import:\n// `import \"@roottale/cms-renderer-next/styles\";`\n// 본 파일에서 side-effect import 하지 않음 — tsup dts build 가 .css\n// side-effect import 처리 못 함 + customer 측 framework 마다 CSS 처리\n// 방식 다르므로 명시적 import 가 명확. README 참조.\n\nimport {\n bindingSrcValue,\n bindingTextValue,\n boundFieldValue,\n extractFaqEntries,\n faqPageJsonLd,\n isAllowedEmbedSrc,\n postsForCollection,\n resolvePostPath,\n type RouteCollection,\n} from \"@roottale/cms-core\";\n\nimport {\n renderSectionNode,\n type CmsBoundContext,\n type SectionRenderContext,\n} from \"./render-section-node.js\";\nimport { RootTalePostCard } from \"./PostCard.js\";\nimport { RootTaleTableOfContents } from \"./TableOfContents.js\";\nimport { themeToCssVars } from \"./theme-css-vars.js\";\nimport {\n collectCategories,\n collectCategoriesWithCounts,\n collectCollectionCategories,\n defaultCategoryHref,\n filterPostsByCategory,\n} from \"./blog-filter.js\";\nimport { attachHeadingIds, extractToc } from \"./toc.js\";\n\ntype CmsPostWithAuthor = CmsPostContent & {\n authorName?: string | null;\n authorImageUrl?: string | null;\n authorBio?: string | null;\n};\n\nfunction authorImageRadiusCss(radius: \"circle\" | \"rounded\" | \"square\"): string {\n if (radius === \"circle\") return \"999px\";\n if (radius === \"rounded\") return \"0.875rem\";\n return \"0\";\n}\n\nexport { RootTalePostCard, type RootTalePostCardProps } from \"./PostCard.js\";\nexport {\n RootTaleTableOfContents,\n type RootTaleTableOfContentsProps,\n} from \"./TableOfContents.js\";\nexport { themeToCssVars, type RootTaleCssVars } from \"./theme-css-vars.js\";\nexport {\n RootTaleFloatingCta,\n type RootTaleFloatingCtaProps,\n type CtaButton,\n type CtaButtonType,\n type FloatingCtaPosition,\n} from \"./FloatingCta.js\";\nexport {\n RootTaleLeadForm,\n type RootTaleLeadFormProps,\n type LeadFormVertical,\n} from \"./LeadForm.js\";\nexport { renderBlock, renderBlocks, renderBoundBlocks } from \"./block-to-react.js\";\nexport {\n collectCategories,\n collectCategoriesWithCounts,\n filterPostsByCategory,\n defaultCategoryHref,\n type BlogCategory,\n type BlogCategoryWithCount,\n} from \"./blog-filter.js\";\nexport {\n attachHeadingIds,\n extractToc,\n headingToId,\n type TocEntry,\n} from \"./toc.js\";\nexport type { RootTaleTheme } from \"@roottale/cms-client/server\";\n\n/**\n * RootTaleBlogList/Post 가 받는 theme prop 의 평가 결과:\n * - `RootTaleTheme` 객체 → 그대로 CSS 변수로 주입\n * - `null` → override 없음 (cms-public.css fallback 그대로)\n * - `undefined` → API key 로 자동 fetch (mysite.roottale.com 의 admin /design)\n */\nexport type RootTaleThemeInput = RootTaleTheme | null | undefined;\n\n/**\n * RootTale 디자인 토큰을 CSS 변수로 자식 트리에 주입한다. 외부 사이트가\n * 여러 RootTale 컴포넌트를 한 레이아웃에 묶을 때, 컴포넌트마다 같은 토큰을\n * fetch 하지 않도록 부모에서 한 번만 fetch 해서 전달하는 용도.\n *\n * ```tsx\n * const theme = await fetchTheme({ apiKey });\n * <RootTaleThemeProvider theme={theme}>\n * <RootTaleBlogList apiKey={apiKey} theme={null} />\n * <RootTaleBlogPost apiKey={apiKey} slugOrId=\"...\" theme={null} />\n * </RootTaleThemeProvider>\n * ```\n *\n * 자식 RootTale 컴포넌트가 `theme={null}` 을 받으면 자체 주입을 건너뛰고\n * 부모의 변수를 그대로 상속. 자식이 `theme` 을 지정하면 자식 변수가 우선.\n */\nexport function RootTaleThemeProvider(props: {\n theme: RootTaleTheme | null | undefined;\n children: ReactNode;\n}): ReactElement {\n const style = themeToCssVars(props.theme ?? null);\n if (!style) return <>{props.children}</>;\n // data-rt-theme (W0-2): 브리지 alias 가 이 wrapper 에서 재계산되도록 하는 scope\n // hook. `--rt-*` inline 변수가 선언된 요소와 같아야 테넌트값이 승리한다\n // (tenant-theme-tokens.md §2 scope 계약). 기존 data-roottale-cms 유지.\n return (\n <div data-roottale-cms=\"theme-root\" data-rt-theme=\"\" style={style}>\n {props.children}\n </div>\n );\n}\n\nexport type RootTaleBlogListProps = {\n apiKey: string;\n baseUrl?: string;\n limit?: number;\n /** Filter by post type (`post` or `page`). Default = `post` only (blog list 의도). */\n type?: CmsPostType;\n /**\n * Build a customer-side URL for a single post. Defaults to `/blog/${slug}`,\n * 또는 `collections` 를 주면 글의 섹션 basePath 기준(`/notice/${slug}` 등).\n */\n postHref?: (post: CmsPostContent) => string;\n /** Rendered when the tenant has no published posts yet. */\n emptyMessage?: string;\n /**\n * 단일 섹션(stream)으로 스코프 (ADR-0060). `collections` 와 함께 줘야 동작한다.\n * 예: `collection=\"notice\"` → 공지 섹션 글만 렌더. **미지정 = 전체** 글(레거시\n * 단일 블로그). 이 prop 없이 다중 스트림 사이트에서 쓰면 공지·블로그가 한 목록에\n * 섞여 나오므로(`/blog` 에 공지가 새어 들어옴), 섹션을 나눈 사이트는 각 목록\n * 페이지에서 반드시 `collection` 으로 스코프해야 한다.\n */\n collection?: string;\n /**\n * 섹션 정의 (ADR-0060). `collection` 스코프와 collection-aware `postHref`(글을\n * 소속 섹션 basePath 로 라우팅) 해석에 쓰인다. admin 에서 동적으로 받으려면\n * `fetchCollections()` 결과를 넘긴다(`collections.md` 참고).\n */\n collections?: readonly RouteCollection[];\n /**\n * 카테고리 필터 칩을 목록 위에 렌더. SSR-only — 칩은 `<a>` 링크이고\n * `?category=<slug>` URL 파라미터로 동작한다(client JS 불요). consumer 는\n * searchParams 의 category 를 `activeCategory` 로 주입.\n */\n showCategoryFilter?: boolean;\n /** 현재 선택 카테고리 slug. 미지정/빈 값 = 전체. searchParams 에서 주입. */\n activeCategory?: string;\n /**\n * 카테고리 칩 링크 URL 빌더. `null` = \"전체\" 칩. 기본 `?category=<slug>`\n * (전체 = `?`). 별도 라우트(`/blog/category/<slug>`)를 쓰려면 override.\n */\n categoryHref?: (slug: string | null) => string;\n /** \"전체\" 칩 라벨. 기본 \"전체\". */\n allCategoriesLabel?: string;\n /**\n * 작가 slug 로 글을 필터 — 작가 아카이브(/blog/author/{slug}) 페이지용. 지정 시\n * 그 작가의 발행 글(post)만 fetch 한다. `fetchAuthors` 로 작가 메타(이름·소개)를\n * 따로 가져와 페이지 헤더에 쓴다.\n */\n author?: string;\n /**\n * RootTale 디자인 토큰. 생략 시 admin /design 에서 저장한 값을 자동 fetch.\n * `null` 을 명시하면 자동 fetch 도 건너뛰고 CSS fallback 사용 (부모\n * `<RootTaleThemeProvider>` 가 변수를 이미 설정한 경우 유용).\n */\n theme?: RootTaleThemeInput;\n};\n\nexport async function RootTaleBlogList(\n props: RootTaleBlogListProps,\n): Promise<ReactElement> {\n const {\n apiKey,\n baseUrl,\n limit,\n type = \"post\",\n collection,\n collections,\n emptyMessage = \"아직 발행된 글이 없습니다.\",\n showCategoryFilter = false,\n activeCategory,\n categoryHref = defaultCategoryHref,\n allCategoriesLabel = \"전체\",\n author,\n theme: themeProp,\n } = props;\n // collections 를 주면 글을 소속 섹션 basePath 로 라우팅(공지→/notice 등),\n // 아니면 레거시 `/blog/${slug}`. 명시 postHref 가 있으면 그게 우선.\n const postHref =\n props.postHref ??\n (collections ? makeCollectionPostHref(collections) : defaultPostHref);\n\n const [page, themeStyle, blogSettings] = await Promise.all([\n fetchPosts({ apiKey, baseUrl, limit, type, ...(author ? { author } : {}) }),\n resolveThemeStyle({ themeProp, apiKey, baseUrl }),\n fetchBlogSettings({ apiKey, baseUrl }).catch(() => DEFAULT_BLOG_SETTINGS),\n ]);\n\n // 섹션 스코프(ADR-0060) — `collection`+`collections` 가 있으면 그 섹션 글만.\n // 미지정이면 전체(레거시). 공지·블로그가 한 목록에 섞이는 누출을 막는다.\n const items =\n collection && collections\n ? postsForCollection(page.items, collections, collection)\n : page.items;\n\n if (items.length === 0) {\n return (\n <div data-roottale-cms=\"list\" style={themeStyle ?? undefined}>\n <p className=\"rt-cms-empty\">{emptyMessage}</p>\n </div>\n );\n }\n\n // 필터 칩은 *전체* 글의 카테고리에서 도출(선택 카테고리로 좁혀도 칩은 고정).\n const categories = showCategoryFilter ? collectCategories(items) : [];\n const visiblePosts = showCategoryFilter\n ? filterPostsByCategory(items, activeCategory)\n : items;\n\n return (\n <div data-roottale-cms=\"list\" style={themeStyle ?? undefined}>\n {showCategoryFilter && categories.length > 0 ? (\n <nav className=\"rt-cms-filter\" aria-label=\"카테고리 필터\">\n <a\n className=\"rt-cms-filter-chip\"\n href={categoryHref(null)}\n data-active={!activeCategory ? \"\" : undefined}\n aria-current={!activeCategory ? \"true\" : undefined}\n >\n {allCategoriesLabel}\n </a>\n {categories.map((c) => (\n <a\n key={c.slug}\n className=\"rt-cms-filter-chip\"\n href={categoryHref(c.slug)}\n data-active={activeCategory === c.slug ? \"\" : undefined}\n aria-current={activeCategory === c.slug ? \"true\" : undefined}\n >\n {c.name}\n </a>\n ))}\n </nav>\n ) : null}\n {visiblePosts.length === 0 ? (\n <p className=\"rt-cms-empty\">{emptyMessage}</p>\n ) : (\n <ul className=\"rt-cms-list\">\n {visiblePosts.map((post) => (\n <li key={post.id} className=\"rt-cms-list-item\">\n <RootTalePostCard\n post={post}\n href={postHref}\n display={resolvePostDisplay(post, blogSettings)}\n />\n </li>\n ))}\n </ul>\n )}\n </div>\n );\n}\n\nexport type RootTaleBlogCategoriesProps = {\n apiKey: string;\n baseUrl?: string;\n limit?: number;\n /** Build a customer-side URL for a category. Defaults to `/blog/categories/<slug>`. */\n categoryHref?: (slug: string) => string;\n /** Rendered when no categories are attached to published posts yet. */\n emptyMessage?: string;\n /**\n * 단일 섹션으로 스코프 (ADR-0060). `collections` 와 함께 줘야 동작. 그 섹션의\n * `categories`(주제 allowlist) 안에서, 그 섹션에 귀속된 글의 카테고리만 집계한다.\n * 미지정 = 전체 글의 카테고리.\n */\n collection?: string;\n /** 섹션 정의 (ADR-0060). `collection` 스코프에 필요. */\n collections?: readonly RouteCollection[];\n /** See `RootTaleBlogListProps['theme']`. */\n theme?: RootTaleThemeInput;\n};\n\nexport async function RootTaleBlogCategories(\n props: RootTaleBlogCategoriesProps,\n): Promise<ReactElement> {\n const {\n apiKey,\n baseUrl,\n limit,\n collection,\n collections,\n categoryHref = (slug) => defaultCategoryHref(slug),\n emptyMessage = \"아직 카테고리가 없습니다.\",\n theme: themeProp,\n } = props;\n\n const [page, themeStyle] = await Promise.all([\n fetchPosts({ apiKey, baseUrl, limit, type: \"post\" }),\n resolveThemeStyle({ themeProp, apiKey, baseUrl }),\n ]);\n const categories =\n collection && collections\n ? collectCollectionCategories(page.items, collections, collection)\n : collectCategoriesWithCounts(page.items);\n\n return (\n <nav\n data-roottale-cms=\"categories\"\n className=\"rt-cms-categories\"\n aria-label=\"블로그 카테고리\"\n style={themeStyle ?? undefined}\n >\n {categories.length === 0 ? (\n <p className=\"rt-cms-empty\">{emptyMessage}</p>\n ) : (\n <ul className=\"rt-cms-category-list\">\n {categories.map((category) => (\n <li key={category.slug} className=\"rt-cms-category-list-item\">\n <a\n className=\"rt-cms-category-link\"\n href={categoryHref(category.slug)}\n >\n <span className=\"rt-cms-category-name\">{category.name}</span>\n <span className=\"rt-cms-category-count\">{category.count}</span>\n </a>\n </li>\n ))}\n </ul>\n )}\n </nav>\n );\n}\n\nexport type RootTaleBlogPostProps = {\n apiKey: string;\n slugOrId: string;\n baseUrl?: string;\n /** Rendered when the slug is not found or not published. */\n notFoundElement?: ReactElement;\n /**\n * When true, prepends an auto-derived `<RootTaleTableOfContents>` before\n * the article body (H2-H4 headings only). Default: false — keep markup\n * stable for customers who already control their own layout.\n */\n showTableOfContents?: boolean;\n /** Title shown above the TOC (only when `showTableOfContents`). */\n tableOfContentsTitle?: string;\n /**\n * 글 하단에 같은 카테고리 최근 글을 N개 노출 (0 = off, 기본 0 — 기존 마크업 보존).\n * 현재 글에 카테고리가 없거나 후보가 없으면 렌더되지 않는다.\n */\n relatedPostsCount?: number;\n /** 관련 글 섹션 제목. 기본 \"관련 글\". */\n relatedPostsTitle?: string;\n /** 관련 글 링크 빌더 — 미지정 시 collections 라우팅 또는 `/blog/{slug}`. */\n postHref?: (post: CmsPostContent) => string;\n /** 다중 스트림 라우팅(ADR-0060) — 관련 글 링크 해석에 사용. */\n collections?: readonly RouteCollection[];\n /** See `RootTaleBlogListProps['theme']`. */\n theme?: RootTaleThemeInput;\n};\n\nexport async function RootTaleBlogPost(\n props: RootTaleBlogPostProps,\n): Promise<ReactElement> {\n const {\n apiKey,\n slugOrId,\n baseUrl,\n notFoundElement,\n showTableOfContents,\n tableOfContentsTitle,\n relatedPostsCount = 0,\n relatedPostsTitle = \"관련 글\",\n collections,\n theme: themeProp,\n } = props;\n const [post, themeStyle, blogSettings] = await Promise.all([\n fetchPost({ apiKey, slugOrId, baseUrl }),\n resolveThemeStyle({ themeProp, apiKey, baseUrl }),\n // 사이트 기본 표시 설정 — 실패해도 렌더는 진행 (안전 기본값).\n fetchBlogSettings({ apiKey, baseUrl }).catch(() => DEFAULT_BLOG_SETTINGS),\n ]);\n\n if (!post) {\n return (\n notFoundElement ?? (\n <div data-roottale-cms=\"post-missing\" style={themeStyle ?? undefined}>\n <p className=\"rt-cms-empty\">글을 찾을 수 없습니다.</p>\n </div>\n )\n );\n }\n\n // 표시 결정: prop > 글별 override(metaJson.display) > 사이트 기본값.\n const display = resolvePostDisplay(post, blogSettings, {\n showTableOfContents,\n tableOfContentsTitle,\n });\n const toc = display.showTableOfContents ? extractToc(post.bodyJson) : [];\n const renderDoc =\n toc.length > 0 ? attachHeadingIds(post.bodyJson, toc) : post.bodyJson;\n // 관련 글 — 같은 카테고리 최근 글. count>0 일 때만 후보 fetch.\n let related: CmsPostContent[] = [];\n if (relatedPostsCount > 0) {\n try {\n const candidates = await fetchPosts({\n apiKey,\n baseUrl,\n type: \"post\",\n limit: 30,\n });\n related = selectRelatedPosts(post, candidates.items, relatedPostsCount);\n } catch {\n /* 후보 fetch 실패 시 관련 글 생략 */\n }\n }\n const relatedHref =\n props.postHref ??\n (collections ? makeCollectionPostHref(collections) : defaultPostHref);\n const authorName = (post as CmsPostWithAuthor).authorName;\n const authorImageUrl = (post as CmsPostWithAuthor).authorImageUrl;\n const postAuthorBio = (post as CmsPostWithAuthor).authorBio;\n const views = typeof post.viewCount === \"number\" ? post.viewCount : null;\n const publishedDate = display.showDate\n ? formatPublishedDate(post.publishedAt)\n : \"\";\n const showTopAuthor = display.showAuthor && Boolean(authorName);\n const displayAuthorName = authorName ?? display.authorProfileName;\n const authorBio =\n postAuthorBio ?? display.authorProfileBio ?? display.authorCardDescription;\n const authorImageUrlForCard =\n authorImageUrl ?? display.authorProfileImageUrl;\n const authorImageAlt = `${displayAuthorName ?? \"\"} 프로필 사진`.trim();\n const authorImageStyle: CSSProperties = {\n borderRadius: authorImageRadiusCss(display.authorProfileImageRadius),\n objectPosition: `${display.authorProfileImagePositionX}% ${display.authorProfileImagePositionY}%`,\n };\n const showAuthorCard = display.showAuthorCard && Boolean(displayAuthorName);\n\n return (\n <article\n data-roottale-cms=\"post\"\n className=\"rt-cms-article\"\n style={themeStyle ?? undefined}\n >\n <header className=\"rt-cms-post-header\">\n {publishedDate || showTopAuthor || views !== null ? (\n <p className=\"rt-cms-meta\">\n {publishedDate ? (\n <span className=\"rt-cms-meta-item\" data-rt-cms-meta=\"date\">\n {publishedDate}\n </span>\n ) : null}\n {showTopAuthor ? (\n <span className=\"rt-cms-meta-item\" data-rt-cms-meta=\"author\">\n 작성자 {authorName}\n </span>\n ) : null}\n {/* ADR-0065 — 사이트 공개 토글 on 일 때만 API 가 viewCount 를 내려준다. */}\n {views !== null ? (\n <span className=\"rt-cms-meta-item\" data-rt-cms-meta=\"views\">\n 조회 {views.toLocaleString(\"ko-KR\")}\n </span>\n ) : null}\n </p>\n ) : null}\n <h1 className=\"rt-cms-title\">{post.title}</h1>\n {post.excerpt ? <p className=\"rt-cms-excerpt\">{post.excerpt}</p> : null}\n </header>\n {renderArchiveRelated(post.archiveRelated, collections)}\n {(() => {\n const tocEl =\n toc.length > 0 ? (\n <RootTaleTableOfContents entries={toc} title={display.tocTitle} />\n ) : null;\n const bodyEl = (\n <div data-roottale-cms=\"body\" className=\"rt-cms-body\">\n {/* ADR-0041 D3 — 블록 바인딩을 공개 API 의 포맷 필드 값으로 치환. */}\n <RenderTiptap\n doc={renderDoc}\n fields={post.fields}\n apiKey={apiKey}\n baseUrl={baseUrl}\n />\n </div>\n );\n // 사이드바 모드 — 넓은 화면에서만 본문 오른쪽 sticky 목차, 좁은 화면은\n // CSS 컨테이너 쿼리로 자동 인라인 폴백(마크업은 동일). 목차 없으면 인라인.\n if (display.tocPosition === \"sidebar\" && tocEl) {\n return (\n <div className=\"rt-cms-layout rt-cms-layout--toc-side\">\n {bodyEl}\n <aside className=\"rt-cms-toc-side\">{tocEl}</aside>\n </div>\n );\n }\n return (\n <>\n {tocEl}\n {bodyEl}\n </>\n );\n })()}\n {showAuthorCard && displayAuthorName ? (\n <section className=\"rt-cms-author-card\" aria-label=\"작성자\">\n {authorImageUrlForCard ? (\n <img\n className=\"rt-cms-author-card__image\"\n src={authorImageUrlForCard}\n alt={authorImageAlt}\n loading=\"lazy\"\n style={authorImageStyle}\n />\n ) : (\n <div className=\"rt-cms-author-card__avatar\" aria-hidden=\"true\">\n {authorInitials(displayAuthorName)}\n </div>\n )}\n <div className=\"rt-cms-author-card__body\">\n <p className=\"rt-cms-author-card__eyebrow\">\n {display.authorCardEyebrow}\n </p>\n <p className=\"rt-cms-author-card__name\">{displayAuthorName}</p>\n {authorBio ? (\n <p className=\"rt-cms-author-card__description\">\n {authorBio}\n </p>\n ) : null}\n </div>\n </section>\n ) : null}\n {renderRelated(related, relatedHref, relatedPostsTitle)}\n {renderPostCta(blogSettings.postCta)}\n {renderFaqJsonLd(post.bodyJson)}\n </article>\n );\n}\n\nfunction renderArchiveRelated(\n groups: readonly CmsArchiveRelatedGroup[],\n collections: readonly RouteCollection[] | undefined,\n): ReactElement | null {\n const visible = groups.filter((group) => group.items.length > 0);\n if (visible.length === 0) return null;\n return (\n <aside className=\"rt-archive-related\" aria-label=\"연관 정보\">\n {visible.map((group) => (\n <section key={group.collectionKey} className=\"rt-archive-related__group\">\n <h2 className=\"rt-archive-related__title\">\n {archiveGroupTitle(group)}\n </h2>\n <ul className=\"rt-archive-related__list\">\n {group.items.map((item) => (\n <li key={item.id} className=\"rt-archive-related__item\">\n <a\n className=\"rt-archive-related__link\"\n href={archivePostHref(item.collectionKey, item.slug, collections)}\n >\n <span className=\"rt-archive-related__item-title\">\n {item.title}\n </span>\n {item.excerpt ? (\n <span className=\"rt-archive-related__item-excerpt\">\n {item.excerpt}\n </span>\n ) : null}\n </a>\n </li>\n ))}\n </ul>\n </section>\n ))}\n </aside>\n );\n}\n\nfunction archiveGroupTitle(group: CmsArchiveRelatedGroup): string {\n if (group.collectionKey === \"conditions\") {\n return `이 증상과 관련된 질환 ${group.items.length}`;\n }\n if (group.collectionKey === \"treatments\") {\n return `관련 치료법 ${group.items.length}`;\n }\n if (group.collectionKey === \"symptoms\") {\n return `관련 증상 ${group.items.length}`;\n }\n return `${group.label} ${group.items.length}`;\n}\n\nfunction archivePostHref(\n collectionKey: string,\n slug: string,\n collections: readonly RouteCollection[] | undefined,\n): string {\n const collection = collections?.find((item) => item.key === collectionKey);\n const basePath = collection?.basePath ?? defaultArchiveBasePath(collectionKey);\n return `${basePath.replace(/\\/+$/, \"\")}/${slug}`;\n}\n\nfunction defaultArchiveBasePath(collectionKey: string): string {\n if (collectionKey === \"symptoms\") return \"/symptoms\";\n if (collectionKey === \"conditions\") return \"/conditions\";\n if (collectionKey === \"treatments\") return \"/treatments\";\n return \"/blog\";\n}\n\n/**\n * 글 하단 고정 CTA — 사이트 블로그 설정에 활성화돼 있으면 모든 글 본문 끝에\n * 동일 CTA 블록을 그린다(리드 전환 유도). href 는 안전 스킴만 통과 — 외부\n * 링크(http/mailto/tel 이 아닌 절대 URL 제외)는 새 탭으로 열되 그 외엔 차단.\n */\nfunction renderPostCta(cta: RootTaleBlogSettings[\"postCta\"]): ReactElement | null {\n if (!cta) return null;\n const href = cta.buttonHref.trim();\n const safe =\n href.startsWith(\"/\") ||\n href.startsWith(\"#\") ||\n /^(https?:|mailto:|tel:)/i.test(href);\n if (!safe) return null;\n const external = /^https?:/i.test(href);\n return (\n <aside data-roottale-cms=\"post-cta\" className=\"rt-cms-post-cta\">\n {cta.title ? <p className=\"rt-cms-post-cta__title\">{cta.title}</p> : null}\n {cta.description ? (\n <p className=\"rt-cms-post-cta__desc\">{cta.description}</p>\n ) : null}\n <a\n className=\"rt-cms-post-cta__btn\"\n href={href}\n target={external ? \"_blank\" : undefined}\n rel={external ? \"noopener noreferrer\" : undefined}\n >\n {cta.buttonLabel}\n </a>\n </aside>\n );\n}\n\n/**\n * 관련 글 섹션 — 같은 카테고리 최근 글. astro renderRelatedHtml 과 마크업·클래스\n * 패리티(.rt-cms-related → cms-public.css 공유).\n */\nfunction renderRelated(\n posts: CmsPostContent[],\n href: (p: CmsPostContent) => string,\n title: string,\n): ReactElement | null {\n if (posts.length === 0) return null;\n return (\n <nav data-roottale-cms=\"related\" className=\"rt-cms-related\" aria-label={title}>\n <h2 className=\"rt-cms-related-title\">{title}</h2>\n <ul className=\"rt-cms-related-list\">\n {posts.map((p) => (\n <li key={p.id} className=\"rt-cms-related-item\">\n <a className=\"rt-cms-related-link\" href={href(p)}>\n {p.featuredImageUrl ? (\n <img\n className=\"rt-cms-related-thumb\"\n src={p.featuredImageUrl}\n alt=\"\"\n loading=\"lazy\"\n />\n ) : null}\n <span className=\"rt-cms-related-post-title\">{p.title}</span>\n </a>\n </li>\n ))}\n </ul>\n </nav>\n );\n}\n\n/**\n * FAQ 블록 → FAQPage 구조화데이터(JSON-LD). `<` escape 로 breakout 차단.\n * astro 렌더러 renderJsonLdScript 와 동일 정책(패리티).\n */\nfunction renderFaqJsonLd(doc: unknown): ReactElement | null {\n const ld = faqPageJsonLd(extractFaqEntries(doc));\n if (!ld) return null;\n const json = JSON.stringify(ld).replace(/</g, \"\\\\u003c\");\n return (\n <script\n type=\"application/ld+json\"\n dangerouslySetInnerHTML={{ __html: json }}\n />\n );\n}\n\nexport function RootTaleFaqJsonLd({\n doc,\n}: {\n doc: unknown;\n}): ReactElement | null {\n return renderFaqJsonLd(doc);\n}\n\nexport type RootTalePageProps = {\n apiKey: string;\n /** Page slug (kebab-case) or UUIDv7 id. */\n slugOrId: string;\n baseUrl?: string;\n /** Rendered when the slug is not found or not published. */\n notFoundElement?: ReactElement;\n /**\n * Render the page title as `<h1>`. Default true — set false when the\n * customer layout already owns the heading.\n */\n showTitle?: boolean;\n /** See `RootTaleBlogListProps['theme']`. */\n theme?: RootTaleThemeInput;\n};\n\n/**\n * 고정 페이지(posts type='page') 렌더러 — RootTaleBlogPost 의 경량판.\n * 날짜·작성자·author card·blog settings 등 블로그 chrome 없이\n * 제목 + 본문만 그린다. 회사 소개·서비스·이용약관 류의 페이지 용도.\n *\n * ```tsx\n * // app/[slug]/page.tsx\n * export default async function CmsPage({ params }) {\n * const { slug } = await params;\n * return <RootTalePage apiKey={process.env.ROOTTALE_API_KEY!} slugOrId={slug} />;\n * }\n * ```\n */\nexport async function RootTalePage(\n props: RootTalePageProps,\n): Promise<ReactElement> {\n const {\n apiKey,\n slugOrId,\n baseUrl,\n notFoundElement,\n showTitle = true,\n theme: themeProp,\n } = props;\n const [post, themeStyle] = await Promise.all([\n fetchPost({ apiKey, slugOrId, baseUrl }),\n resolveThemeStyle({ themeProp, apiKey, baseUrl }),\n ]);\n\n if (!post) {\n return (\n notFoundElement ?? (\n <div data-roottale-cms=\"page-missing\" style={themeStyle ?? undefined}>\n <p className=\"rt-cms-empty\">페이지를 찾을 수 없습니다.</p>\n </div>\n )\n );\n }\n\n return (\n <article\n data-roottale-cms=\"page\"\n className=\"rt-cms-article\"\n style={themeStyle ?? undefined}\n >\n {showTitle ? (\n <header className=\"rt-cms-post-header\">\n <h1 className=\"rt-cms-title\">{post.title}</h1>\n </header>\n ) : null}\n <div data-roottale-cms=\"body\" className=\"rt-cms-body\">\n <RenderTiptap\n doc={post.bodyJson}\n fields={post.fields}\n apiKey={apiKey}\n baseUrl={baseUrl}\n />\n </div>\n </article>\n );\n}\n\nfunction authorInitials(name: string): string {\n const trimmed = name.trim();\n if (!trimmed) return \"\";\n const parts = trimmed.split(/\\s+/).filter(Boolean);\n if (parts.length >= 2) {\n return `${parts[0]?.[0] ?? \"\"}${parts[1]?.[0] ?? \"\"}`.toUpperCase();\n }\n return [...trimmed].slice(0, 2).join(\"\").toUpperCase();\n}\n\n/**\n * theme prop 의 3가지 입력값 (object / null / undefined) 을 React inline style\n * 로 변환. `null` 또는 빈 객체는 style 자체를 생략 → 부모에서 이미 변수를\n * 설정했다면 자식이 덮어쓰지 않음.\n */\nasync function resolveThemeStyle(input: {\n themeProp: RootTaleThemeInput;\n apiKey: string;\n baseUrl?: string;\n}): Promise<CSSProperties | null> {\n const { themeProp, apiKey, baseUrl } = input;\n if (themeProp === null) return null;\n if (themeProp !== undefined) return themeToCssVars(themeProp);\n // undefined → auto-fetch. theme fetch 실패는 비치명적 — 빈 토큰으로 fallback.\n try {\n const fetched = await fetchTheme({ apiKey, baseUrl });\n return themeToCssVars(fetched);\n } catch {\n return null;\n }\n}\n\nfunction defaultPostHref(post: CmsPostContent): string {\n return `/blog/${post.slug}`;\n}\n\n/**\n * collections 기반 postHref — 글을 소속 섹션 basePath 로 라우팅(공지→/notice,\n * 블로그→/blog). 어느 섹션에도 안 속하면 레거시 `/blog/${slug}` 로 fallback.\n */\nfunction makeCollectionPostHref(\n collections: readonly RouteCollection[],\n): (post: CmsPostContent) => string {\n return (post) => resolvePostPath(post, collections) ?? defaultPostHref(post);\n}\n\nfunction formatPublishedDate(iso: string): string {\n const d = new Date(iso);\n if (Number.isNaN(d.getTime())) return \"\";\n return new Intl.DateTimeFormat(\"ko-KR\", {\n year: \"numeric\",\n month: \"2-digit\",\n day: \"2-digit\",\n }).format(d);\n}\n\n/**\n * Minimal Tiptap doc renderer. Walks the Tiptap doc tree and emits semantic\n * HTML. This is intentionally tiny — richer extensions (image asset URL\n * resolution, TableOfContents, FloatingCta) belong in followup §5.7 #5.\n *\n * Browser-side sanitization is unnecessary here because output is rendered\n * server-side from data the customer-trusted API returned. If the customer\n * site federates with untrusted authors later, layer a sanitizer in front\n * of this component.\n */\nexport function RenderTiptap({\n doc,\n fields,\n apiKey,\n baseUrl,\n}: {\n doc: Record<string, unknown>;\n /**\n * ADR-0041 D3 — 공개 API `post.fields` (포맷된 커스텀 필드 값). 전달 시\n * 블록 바인딩(`roottale/field`)을 필드 값으로 치환 — 값 없으면 본문이\n * fallback 으로 그대로 렌더. astro `renderTiptapDoc(doc, { fields })` 동등.\n */\n fields?: Record<string, unknown> | null;\n /** Enables cms-bound section nodes (`blog-list`, `lead-form`, `contact`). */\n apiKey?: string;\n /** Optional RootTale API base URL for cms-bound section fetches. */\n baseUrl?: string;\n}): ReactElement {\n const rawContent = (doc as TiptapDoc).content;\n const content: TiptapNode[] = Array.isArray(rawContent) ? rawContent : [];\n // 목차 블록이 있으면 헤딩 id 부여 + 항목 수집(앵커 대상). astro 와 동일.\n const hasToc = content.some((n) => n?.type === \"tableOfContents\");\n const docRecord = doc as Record<string, unknown>;\n const entries = hasToc ? extractToc(docRecord) : [];\n const renderDoc =\n hasToc && entries.length > 0 ? attachHeadingIds(docRecord, entries) : docRecord;\n const nodes: TiptapNode[] = Array.isArray((renderDoc as TiptapDoc).content)\n ? ((renderDoc as TiptapDoc).content as TiptapNode[])\n : content;\n const cms: CmsBoundContext | null = apiKey ? { apiKey, baseUrl } : null;\n const context: RenderNodeContext = {\n fields: fields ?? null,\n section: {\n cms,\n isDev: isDevEnv,\n renderBlogList: (props) => <RootTaleBlogList {...props} />,\n },\n };\n return (\n <>\n {nodes.map((node, i) =>\n node?.type === \"tableOfContents\" ? (\n <RootTaleTableOfContents\n key={i}\n entries={entries}\n title={\n typeof node.attrs?.title === \"string\" ? node.attrs.title : \"목차\"\n }\n />\n ) : (\n renderNode(node, i, context)\n ),\n )}\n </>\n );\n}\n\ntype FieldsRecord = Record<string, unknown> | null;\ntype RenderNodeContext = {\n fields: FieldsRecord;\n section: SectionRenderContext;\n};\n\n/** content 바인딩 해석 — 항상 plain text (D4, raw HTML 금지). */\nfunction boundContentText(\n node: TiptapNode,\n fields: FieldsRecord,\n): string | undefined {\n return bindingTextValue(\n boundFieldValue(node.attrs?.bindings, \"content\", fields),\n );\n}\n\ntype TiptapDoc = { type?: string; content?: TiptapNode[] };\n\ntype TiptapMark = { type?: string; attrs?: Record<string, unknown> };\n\ntype TiptapNode = {\n type?: string;\n text?: string;\n attrs?: Record<string, unknown>;\n content?: TiptapNode[];\n marks?: TiptapMark[];\n};\n\n/**\n * Defense-in-depth: even though admin LinkButton.normalizeUrl filters input,\n * legacy imports / direct DB / external tenants can still emit unsafe href.\n * Allow only http(s)/mailto/tel/root-relative/fragment/query — falls back to \"#\".\n */\nfunction isSafeHref(value: unknown): value is string {\n if (typeof value !== \"string\") return false;\n const v = value.trim();\n if (v.length === 0) return false;\n if (v.startsWith(\"/\") || v.startsWith(\"#\") || v.startsWith(\"?\")) return true;\n return /^(https?:|mailto:|tel:)/i.test(v);\n}\n\nfunction alignStyle(node: TiptapNode): CSSProperties | undefined {\n const align = node.attrs?.textAlign;\n if (typeof align !== \"string\" || align === \"\" || align === \"left\") return undefined;\n if (align === \"center\" || align === \"right\" || align === \"justify\") {\n return { textAlign: align };\n }\n return undefined;\n}\n\n/** 표 셀의 colSpan/rowSpan (1 초과일 때만 적용, 아니면 undefined). */\nfunction tableCellSpans(node: TiptapNode): {\n colSpan: number | undefined;\n rowSpan: number | undefined;\n} {\n const colspan = Number(node.attrs?.colspan);\n const rowspan = Number(node.attrs?.rowspan);\n return {\n colSpan: Number.isFinite(colspan) && colspan > 1 ? colspan : undefined,\n rowSpan: Number.isFinite(rowspan) && rowspan > 1 ? rowspan : undefined,\n };\n}\n\n/** dts 빌드는 node types 없이 돈다 — process 는 globalThis 경유로 안전 접근. */\nfunction isDevEnv(): boolean {\n const env =\n (globalThis as { process?: { env?: Record<string, string | undefined> } })\n .process?.env ?? {};\n return env.NODE_ENV !== \"production\";\n}\n\nfunction renderNode(\n node: TiptapNode,\n key: number,\n context: RenderNodeContext = {\n fields: null,\n section: {\n cms: null,\n isDev: isDevEnv,\n renderBlogList: (props) => <RootTaleBlogList {...props} />,\n },\n },\n): ReactElement | string | null {\n if (!node || typeof node !== \"object\") return null;\n const children = Array.isArray(node.content)\n ? node.content.map((child, i) => renderNode(child, i, context))\n : undefined;\n\n switch (node.type) {\n case \"paragraph\": {\n const bound = boundContentText(node, context.fields);\n return (\n <p key={key} style={alignStyle(node)}>\n {bound ?? children}\n </p>\n );\n }\n case \"heading\": {\n const rawLevel = Number(node.attrs?.level ?? 2);\n const level = Math.min(Math.max(Number.isFinite(rawLevel) ? rawLevel : 2, 2), 4);\n const idAttr = node.attrs?.id;\n const id = typeof idAttr === \"string\" && idAttr.length > 0 ? idAttr : undefined;\n const style = alignStyle(node);\n const inner = boundContentText(node, context.fields) ?? children;\n if (level === 2) return <h2 key={key} id={id} style={style}>{inner}</h2>;\n if (level === 3) return <h3 key={key} id={id} style={style}>{inner}</h3>;\n return <h4 key={key} id={id} style={style}>{inner}</h4>;\n }\n case \"bulletList\":\n return <ul key={key}>{children}</ul>;\n case \"orderedList\":\n return <ol key={key}>{children}</ol>;\n case \"listItem\":\n return <li key={key}>{children}</li>;\n case \"blockquote\":\n return <blockquote key={key}>{children}</blockquote>;\n case \"codeBlock\":\n return (\n <pre key={key}>\n <code>{children}</code>\n </pre>\n );\n case \"horizontalRule\":\n return <hr key={key} />;\n case \"hardBreak\":\n return <br key={key} />;\n case \"image\": {\n // ADR-0041 D3 — src 바인딩(포맷된 MediaRef.url / https URL)이 있으면 우선.\n const boundSrc = bindingSrcValue(\n boundFieldValue(node.attrs?.bindings, \"src\", context.fields),\n );\n const src = boundSrc ?? node.attrs?.src;\n if (typeof src !== \"string\" || src.length === 0) return null;\n const alt = typeof node.attrs?.alt === \"string\" ? node.attrs.alt : \"\";\n const title = typeof node.attrs?.title === \"string\" ? node.attrs.title : undefined;\n // width/height — CLS 대책. CSS(max-inline-size:100%; block-size:auto)와\n // 조합돼 반응형 유지. 에디터/WP import 가 보존한 치수가 있을 때만 출력.\n const width =\n typeof node.attrs?.width === \"number\" && node.attrs.width > 0\n ? node.attrs.width\n : undefined;\n const height =\n typeof node.attrs?.height === \"number\" && node.attrs.height > 0\n ? node.attrs.height\n : undefined;\n return (\n <img\n key={key}\n src={src}\n alt={alt}\n title={title}\n width={width}\n height={height}\n loading=\"lazy\"\n />\n );\n }\n case \"columns\":\n return <div key={key} className=\"rt-columns\">{children}</div>;\n case \"column\":\n return <div key={key} className=\"rt-column\">{children}</div>;\n case \"table\":\n return (\n <div key={key} className=\"rt-table-wrap\">\n <table className=\"rt-table\">\n <tbody>{children}</tbody>\n </table>\n </div>\n );\n case \"tableRow\":\n return <tr key={key}>{children}</tr>;\n case \"tableHeader\": {\n const { colSpan, rowSpan } = tableCellSpans(node);\n return <th key={key} colSpan={colSpan} rowSpan={rowSpan}>{children}</th>;\n }\n case \"tableCell\": {\n const { colSpan, rowSpan } = tableCellSpans(node);\n return <td key={key} colSpan={colSpan} rowSpan={rowSpan}>{children}</td>;\n }\n case \"embed\":\n return renderEmbed(node, key);\n case \"faq\":\n return (\n <div key={key} data-roottale-cms=\"faq\" className=\"rt-cms-faq\">\n {children}\n </div>\n );\n case \"faqItem\":\n return (\n <details key={key} className=\"rt-cms-faq-item\">\n {children}\n </details>\n );\n case \"faqQuestion\":\n return (\n <summary key={key} className=\"rt-cms-faq-q\">\n {children}\n </summary>\n );\n case \"faqAnswer\":\n return (\n <div key={key} className=\"rt-cms-faq-a\">\n {children}\n </div>\n );\n case \"tableOfContents\":\n // 목차는 문서 전체 컨텍스트가 필요해 RenderTiptap 최상위에서 처리.\n // 중첩 등장 시엔 무시(헤딩 수집 컨텍스트 없음).\n return null;\n case \"section\":\n return renderSectionNode(node, key, context.section);\n case \"text\":\n return renderText(node, key);\n default:\n return children ? <span key={key}>{children}</span> : null;\n }\n}\n\n/**\n * 영상 임베드 노드 렌더. `isAllowedEmbedSrc` 로 allowlist host(https) 만 iframe\n * 출력 — legacy/import/외부 테넌트가 남긴 임의 src 는 원본 링크로 fallback.\n */\nfunction renderEmbed(node: TiptapNode, key: number): ReactElement | null {\n const src = node.attrs?.src;\n if (isAllowedEmbedSrc(src)) {\n const provider =\n typeof node.attrs?.provider === \"string\" ? node.attrs.provider : undefined;\n return (\n <div\n key={key}\n className=\"rt-embed\"\n data-provider={provider}\n style={{ aspectRatio: \"16 / 9\" }}\n >\n <iframe\n src={src}\n title=\"임베드된 영상\"\n loading=\"lazy\"\n allowFullScreen\n referrerPolicy=\"strict-origin-when-cross-origin\"\n allow=\"accelerometer; clipboard-write; encrypted-media; picture-in-picture; web-share\"\n style={{ width: \"100%\", height: \"100%\", border: 0 }}\n />\n </div>\n );\n }\n const orig = node.attrs?.originalUrl;\n if (isSafeHref(orig)) {\n return (\n <p key={key} className=\"rt-embed-fallback\">\n <a href={orig} rel=\"noopener noreferrer\" target=\"_blank\">\n {orig}\n </a>\n </p>\n );\n }\n return null;\n}\n\nfunction renderText(node: TiptapNode, key: number): ReactElement | string {\n const text = node.text ?? \"\";\n const marks = node.marks ?? [];\n if (marks.length === 0) return text;\n let element: ReactElement | string = text;\n for (const mark of marks) {\n element = applyMark(mark, element);\n }\n return <span key={key}>{element}</span>;\n}\n\nfunction applyMark(mark: TiptapMark, child: ReactElement | string): ReactElement {\n switch (mark.type) {\n case \"bold\":\n return <strong>{child}</strong>;\n case \"italic\":\n return <em>{child}</em>;\n case \"underline\":\n return <u>{child}</u>;\n case \"strike\":\n return <s>{child}</s>;\n case \"code\":\n return <code>{child}</code>;\n case \"highlight\": {\n const color = mark.attrs?.color;\n const style =\n typeof color === \"string\" && color.length > 0\n ? { background: color }\n : undefined;\n return <mark style={style}>{child}</mark>;\n }\n case \"textStyle\": {\n const color = mark.attrs?.color;\n const style =\n typeof color === \"string\" && color.length > 0 ? { color } : undefined;\n if (!style) return <>{child}</>;\n return <span style={style}>{child}</span>;\n }\n case \"link\": {\n const raw = mark.attrs?.href;\n const href = isSafeHref(raw) ? raw : \"#\";\n return (\n <a href={href} rel=\"noopener noreferrer\" target=\"_blank\">\n {child}\n </a>\n );\n }\n default:\n return <span>{child}</span>;\n }\n}\n","/**\n * `@roottale/cms-renderer-next` — RootTaleLeadForm RSC.\n *\n * 외부 고객 사이트(roottale.com, 고객사 외주)의 진단 신청 폼.\n *\n * 권장 연동(API key 하나): `action` 을 *사이트 자체* 의 Route Handler / 폼\n * action 으로 두고, 그 서버 코드가 `@roottale/cms-client` 의 `submitInquiry()` 로\n * 블로그 조회와 동일한 `ROOTTALE_API_KEY` 를 실어 intake API 로 forward 한다. 키가\n * tenant 를 식별 → 리드가 그 tenant 의 admin /crm 으로 라우팅, `tenant_slug` 불필요.\n * (API key 는 브라우저로 노출 금지 — 직접 api.roottale.com 으로 POST 하지 말 것.)\n *\n * PII 는 server 측에서 암호화 후 `inquiries` insert, 302 `?ok=1` / `?err=*` redirect.\n *\n * vertical prop:\n * - 미지정 = \"분야 선택\" select 노출 (consulting/medical/tax/legal)\n * - 지정 = hidden input + 표시 라벨 + vertical 별 추가 필드\n * (medical = 국외이전 동의 — ADR-0018)\n *\n * redirectPath prop:\n * - 외부 사이트가 폼 제출 후 돌아갈 path (API 가 allowlist 검증, ?ok=1 / ?err=*)\n *\n * 0 JS RSC. customer-facing className 전체 `.rt-cms-*` prefix, scoped via\n * `[data-roottale-cms]` (cms-public.css). customer override 자유.\n */\n\nimport type { ReactElement } from \"react\";\n\nexport type LeadFormVertical = \"consulting\" | \"medical\" | \"tax\" | \"legal\";\n\nexport interface RootTaleLeadFormProps {\n /**\n * 폼 제출 대상 — *사이트 자체* 의 server 엔드포인트(Route Handler / form action).\n * 그 서버 코드가 `cms-client` `submitInquiry()` 로 API key 를 실어 forward 한다.\n * 예: `/api/inquiry`. (API key 노출 위험 때문에 intake API 직접 지정 금지.)\n */\n action: string;\n /**\n * vertical 고정. 미지정 시 select 노출.\n * `medical` 일 때 국외이전 동의 체크박스 추가 (ADR-0018).\n */\n vertical?: LeadFormVertical;\n /**\n * 폼 제출 후 API가 돌려보낼 절대 URL.\n * tenant-api `LEAD_INTAKE_ALLOWED_ORIGINS` allowlist 검증. fail = fallback.\n * 미지정 시 tenant-api env의 LEAD_INTAKE_REDIRECT_BASE 사용.\n */\n redirectUrl?: string;\n heading?: string;\n description?: string;\n submitLabel?: string;\n /** 추가 클래스 (customer 디자인 hook). */\n className?: string;\n}\n\nconst VERTICAL_LABEL: Record<LeadFormVertical, string> = {\n consulting: \"컨설팅\",\n medical: \"의료\",\n tax: \"세무\",\n legal: \"법률\",\n};\n\nconst VERTICAL_OPTIONS: { value: LeadFormVertical; label: string }[] = [\n { value: \"consulting\", label: \"컨설팅 / 일반\" },\n { value: \"medical\", label: \"의료 (병의원·치과·한의원)\" },\n { value: \"tax\", label: \"세무 (세무사·회계사)\" },\n { value: \"legal\", label: \"법률 (법무법인·변호사)\" },\n];\n\nexport function RootTaleLeadForm(\n props: RootTaleLeadFormProps,\n): ReactElement {\n const {\n action,\n vertical,\n redirectUrl,\n heading,\n description,\n submitLabel = \"진단 신청\",\n className,\n } = props;\n\n const formClass = [\"rt-cms-lead-form\", className].filter(Boolean).join(\" \");\n\n return (\n <form\n method=\"post\"\n action={action}\n data-roottale-cms=\"lead-form\"\n className={formClass}\n >\n {heading ? (\n <h2 className=\"rt-cms-lead-heading\">{heading}</h2>\n ) : null}\n {description ? (\n <p className=\"rt-cms-lead-description\">{description}</p>\n ) : null}\n\n {redirectUrl ? (\n <input type=\"hidden\" name=\"_redirect_url\" value={redirectUrl} />\n ) : null}\n\n {vertical ? (\n <>\n <input type=\"hidden\" name=\"vertical\" value={vertical} />\n <p className=\"rt-cms-lead-vertical-label\">\n 분야: <strong>{VERTICAL_LABEL[vertical]}</strong>\n </p>\n </>\n ) : (\n <label className=\"rt-cms-field\">\n <span className=\"rt-cms-field-label\">\n 분야 <span aria-hidden=\"true\">*</span>\n </span>\n <select\n name=\"vertical\"\n required\n className=\"rt-cms-field-input rt-cms-field-select\"\n defaultValue=\"\"\n >\n <option value=\"\" disabled>\n 선택하세요\n </option>\n {VERTICAL_OPTIONS.map((opt) => (\n <option key={opt.value} value={opt.value}>\n {opt.label}\n </option>\n ))}\n </select>\n </label>\n )}\n\n <label className=\"rt-cms-field\">\n <span className=\"rt-cms-field-label\">\n 이름 <span aria-hidden=\"true\">*</span>\n </span>\n <input\n type=\"text\"\n name=\"contact_name\"\n required\n autoComplete=\"name\"\n className=\"rt-cms-field-input\"\n />\n </label>\n\n <label className=\"rt-cms-field\">\n <span className=\"rt-cms-field-label\">\n 사업체명 <span aria-hidden=\"true\">*</span>\n </span>\n <input\n type=\"text\"\n name=\"business_name\"\n required\n autoComplete=\"organization\"\n className=\"rt-cms-field-input\"\n />\n </label>\n\n <label className=\"rt-cms-field\">\n <span className=\"rt-cms-field-label\">\n 이메일 <span aria-hidden=\"true\">*</span>\n </span>\n <input\n type=\"email\"\n name=\"email\"\n required\n autoComplete=\"email\"\n className=\"rt-cms-field-input\"\n />\n </label>\n\n <label className=\"rt-cms-field\">\n <span className=\"rt-cms-field-label\">\n 전화번호 <span aria-hidden=\"true\">*</span>\n </span>\n <input\n type=\"tel\"\n name=\"phone\"\n required\n autoComplete=\"tel\"\n inputMode=\"tel\"\n pattern={\"0\\\\d{1,2}-?\\\\d{3,4}-?\\\\d{4}\"}\n placeholder=\"010-1234-5678\"\n className=\"rt-cms-field-input\"\n />\n </label>\n\n <label className=\"rt-cms-field\">\n <span className=\"rt-cms-field-label\">현재 사이트 URL (선택)</span>\n <input\n type=\"url\"\n name=\"current_site_url\"\n placeholder=\"https://\"\n className=\"rt-cms-field-input\"\n />\n <span className=\"rt-cms-field-hint\">없으면 비워두세요.</span>\n </label>\n\n <label className=\"rt-cms-lead-consent\">\n <input type=\"checkbox\" name=\"privacy_consent\" required />\n <span>\n <strong>(필수)</strong> 개인정보 수집·이용에 동의합니다. (수집 항목:\n 이름·연락처·이메일·사업체명. 보관 기간: 문의 종료 후 3년)\n </span>\n </label>\n\n {vertical === \"medical\" ? (\n <label className=\"rt-cms-lead-consent\">\n <input\n type=\"checkbox\"\n name=\"overseas_transfer_consent\"\n required\n />\n <span>\n <strong>(필수, 의료)</strong> 의료 PII 의 국외이전(보관·처리)에\n 동의합니다. (의료법 §21·개인정보보호법 §28 — ADR-0018)\n </span>\n </label>\n ) : null}\n\n <div className=\"rt-cms-lead-actions\">\n <button type=\"submit\" className=\"rt-cms-lead-submit\">\n {submitLabel}\n </button>\n </div>\n </form>\n );\n}\n","/**\n * 빌트인 섹션 템플릿 — MUI-free, 0 JS RSC (ADR-0040 P2).\n *\n * 클래스는 전부 `rt-tpl-*` prefix, 스코프는 섹션 루트의 `data-roottale-tpl`\n * (templates.css 가 `[data-roottale-tpl] :where(…)` 로 zero-specificity 적용 —\n * customer 는 plain class / Tailwind utility 로 자유 override, cms-public.css\n * 와 동일 계약).\n *\n * 토큰은 `--rt-*` CSS 변수 + 정적 fallback — `@roottale/tokens`(private) 미의존\n * (RFC §②). admin /design 값은 `RootTaleThemeProvider` 가 주입.\n * allow: SIZE_OK — W1-2 소유 계약이 Trust·Contact 템플릿을 이 파일에 고정한다.\n */\nimport type { CSSProperties, ReactElement } from \"react\";\nimport { isAllowedEmbedSrc } from \"@roottale/cms-core\";\n\nimport { RootTaleLeadForm } from \"../LeadForm.js\";\nimport type {\n ContactSection,\n CtaBannerSection,\n FaqSection,\n HeroSection,\n RichTextSection,\n SectionCta,\n ServicesSection,\n TrustSection,\n} from \"./site-config.js\";\n\n/** 섹션 `theme`(--rt-* 변수 override)를 inline style 로. */\nexport function sectionThemeStyle(\n theme: Record<string, string> | undefined,\n): CSSProperties | undefined {\n if (!theme) return undefined;\n const style: Record<string, string> = {};\n for (const [key, value] of Object.entries(theme)) {\n if (key.startsWith(\"--\")) style[key] = value;\n }\n return Object.keys(style).length > 0 ? (style as CSSProperties) : undefined;\n}\n\nfunction CtaLink({\n cta,\n className,\n track,\n}: {\n cta: SectionCta;\n className?: string;\n track: \"cta_banner\" | \"cta_hero\";\n}): ReactElement {\n return (\n <a\n className={className ?? \"rt-tpl-btn rt-tpl-btn--primary\"}\n href={cta.href}\n data-track={track}\n >\n {cta.label}\n </a>\n );\n}\n\n/** 빌트인 `hero`. variant: split(기본) / image / minimal. */\nexport function TemplateHero(section: HeroSection): ReactElement {\n const { eyebrow, title, description, imageSrc, imageAlt, cta } =\n section.props;\n const variant = section.variant;\n const showImage = variant !== \"minimal\" && imageSrc;\n return (\n <section\n data-roottale-tpl=\"hero\"\n data-variant={variant}\n id={section.id}\n className=\"rt-tpl-section rt-tpl-hero\"\n style={sectionThemeStyle(section.theme)}\n >\n <div className=\"rt-tpl-container rt-tpl-hero-inner\">\n <div className=\"rt-tpl-hero-copy\">\n {eyebrow ? <p className=\"rt-tpl-hero-eyebrow\">{eyebrow}</p> : null}\n <h1 className=\"rt-tpl-hero-title\">\n {title.split(\"\\n\").map((line, i) => (\n <span key={i} className=\"rt-tpl-hero-title-line\">\n {line}\n </span>\n ))}\n </h1>\n {description ? (\n <p className=\"rt-tpl-hero-desc\">{description}</p>\n ) : null}\n {cta ? <CtaLink cta={cta} track=\"cta_hero\" /> : null}\n </div>\n {showImage ? (\n <div className=\"rt-tpl-hero-media\">\n <img\n className=\"rt-tpl-hero-img\"\n src={imageSrc}\n alt={imageAlt ?? \"\"}\n loading=\"eager\"\n />\n </div>\n ) : null}\n </div>\n </section>\n );\n}\n\n/** 빌트인 `rich-text`. 문단은 `\\n\\n` 분리. */\nexport function TemplateRichText(section: RichTextSection): ReactElement {\n const { heading, body } = section.props;\n return (\n <section\n data-roottale-tpl=\"rich-text\"\n id={section.id}\n className=\"rt-tpl-section rt-tpl-rich-text\"\n style={sectionThemeStyle(section.theme)}\n >\n <div className=\"rt-tpl-container\">\n {heading ? <h2 className=\"rt-tpl-heading\">{heading}</h2> : null}\n {body.split(\"\\n\\n\").map((para, i) => (\n <p key={i} className=\"rt-tpl-rich-text-p\">\n {para}\n </p>\n ))}\n </div>\n </section>\n );\n}\n\n/** 빌트인 `services`. variant: grid(기본) / list. */\nexport function TemplateServices(section: ServicesSection): ReactElement {\n const { heading, description, items } = section.props;\n return (\n <section\n data-roottale-tpl=\"services\"\n data-variant={section.variant}\n id={section.id}\n className=\"rt-tpl-section rt-tpl-services\"\n style={sectionThemeStyle(section.theme)}\n >\n <div className=\"rt-tpl-container\">\n {heading ? <h2 className=\"rt-tpl-heading\">{heading}</h2> : null}\n {description ? (\n <p className=\"rt-tpl-section-desc\">{description}</p>\n ) : null}\n <ul className=\"rt-tpl-services-list\">\n {items.map((item, i) => {\n const body = (\n <>\n <h3 className=\"rt-tpl-services-title\">{item.title}</h3>\n {item.description ? (\n <p className=\"rt-tpl-services-desc\">{item.description}</p>\n ) : null}\n </>\n );\n return (\n <li key={i} className=\"rt-tpl-services-item\">\n {item.href ? (\n <a\n className=\"rt-tpl-services-link\"\n href={item.href}\n data-track=\"service_card\"\n >\n {body}\n </a>\n ) : (\n body\n )}\n </li>\n );\n })}\n </ul>\n </div>\n </section>\n );\n}\n\n/** 빌트인 `faq`. `<details>` 0 JS + FAQPage JSON-LD (기본 on). */\nexport function TemplateFaq(section: FaqSection): ReactElement {\n const { heading, items, jsonLd } = section.props;\n return (\n <section\n data-roottale-tpl=\"faq\"\n id={section.id}\n className=\"rt-tpl-section rt-tpl-faq\"\n style={sectionThemeStyle(section.theme)}\n >\n <div className=\"rt-tpl-container\">\n {heading ? <h2 className=\"rt-tpl-heading\">{heading}</h2> : null}\n <div className=\"rt-tpl-faq-list\">\n {items.map((item, i) => (\n <details key={i} className=\"rt-tpl-faq-item\">\n <summary className=\"rt-tpl-faq-q\">{item.question}</summary>\n <p className=\"rt-tpl-faq-a\">{item.answer}</p>\n </details>\n ))}\n </div>\n </div>\n {jsonLd ? (\n <script\n type=\"application/ld+json\"\n dangerouslySetInnerHTML={{\n __html: JSON.stringify({\n \"@context\": \"https://schema.org\",\n \"@type\": \"FAQPage\",\n mainEntity: items.map((item) => ({\n \"@type\": \"Question\",\n name: item.question,\n acceptedAnswer: { \"@type\": \"Answer\", text: item.answer },\n })),\n }),\n }}\n />\n ) : null}\n </section>\n );\n}\n\n/** 빌트인 `cta-banner`. */\nexport function TemplateCtaBanner(section: CtaBannerSection): ReactElement {\n const { title, description, cta } = section.props;\n return (\n <section\n data-roottale-tpl=\"cta-banner\"\n id={section.id}\n className=\"rt-tpl-section rt-tpl-cta-banner\"\n style={sectionThemeStyle(section.theme)}\n >\n <div className=\"rt-tpl-container rt-tpl-cta-banner-inner\">\n <h2 className=\"rt-tpl-cta-banner-title\">{title}</h2>\n {description ? (\n <p className=\"rt-tpl-cta-banner-desc\">{description}</p>\n ) : null}\n {cta ? <CtaLink cta={cta} track=\"cta_banner\" /> : null}\n </div>\n </section>\n );\n}\n\nfunction TrustCredentialIcon(): ReactElement {\n return (\n <svg\n className=\"rt-cred__icon\"\n viewBox=\"0 0 16 16\"\n aria-hidden=\"true\"\n focusable=\"false\"\n >\n <path d=\"M8 1.75 13 3.5v3.75c0 3-2 5.45-5 7-3-1.55-5-4-5-7V3.5z\" />\n <path d=\"m5.75 8 1.45 1.45 3.2-3.4\" />\n </svg>\n );\n}\n\nexport function TemplateTrust(section: TrustSection): ReactElement {\n const { heading, metrics, credentials, logos, disclaimer } = section.props;\n return (\n <section\n data-roottale-tpl=\"trust\"\n data-variant={section.variant}\n id={section.id}\n className=\"rt-tpl-section rt-tpl-trust\"\n style={sectionThemeStyle(section.theme)}\n >\n <div className=\"rt-tpl-container rt-tpl-trust-inner\">\n {heading ? <h2 className=\"rt-tpl-heading\">{heading}</h2> : null}\n {section.variant === \"stat-band\" ? (\n <div className=\"rt-tpl-trust-stat-band\">\n {(metrics ?? []).map((metric) => (\n <div\n key={`${metric.value}-${metric.label}`}\n className=\"rt-stat\"\n data-variant=\"band-item\"\n >\n <span className=\"rt-stat__value\">\n {metric.value}\n {metric.unit ? (\n <span className=\"rt-stat__unit\">{metric.unit}</span>\n ) : null}\n </span>\n <span className=\"rt-stat__label\">{metric.label}</span>\n {metric.source ? (\n <span className=\"rt-stat__source\">{metric.source}</span>\n ) : null}\n </div>\n ))}\n </div>\n ) : null}\n {section.variant === \"logo-cloud\" ? (\n <ul className=\"rt-tpl-trust-logo-list\">\n {(logos ?? []).map((logo) => (\n <li key={`${logo.src}-${logo.alt}`} className=\"rt-tpl-trust-logo-item\">\n {logo.href ? (\n <a className=\"rt-tpl-trust-logo-link\" href={logo.href}>\n <img\n className=\"rt-tpl-trust-logo\"\n src={logo.src}\n alt={logo.alt}\n loading=\"lazy\"\n />\n </a>\n ) : (\n <img\n className=\"rt-tpl-trust-logo\"\n src={logo.src}\n alt={logo.alt}\n loading=\"lazy\"\n />\n )}\n </li>\n ))}\n </ul>\n ) : null}\n {section.variant === \"feature-grid\" ? (\n <ul className=\"rt-tpl-trust-cred-list\">\n {(credentials ?? []).map((credential) => (\n <li key={`${credential.name}-${credential.issuer ?? \"\"}`}>\n <span className=\"rt-cred\" data-kind={credential.kind}>\n <TrustCredentialIcon />\n <span className=\"rt-cred__label\">{credential.name}</span>\n {credential.issuer ? (\n <span className=\"rt-cred__issuer\">{credential.issuer}</span>\n ) : null}\n </span>\n </li>\n ))}\n </ul>\n ) : null}\n {disclaimer ? (\n <p className=\"rt-tpl-trust-disclaimer\">{disclaimer}</p>\n ) : null}\n </div>\n </section>\n );\n}\n\nfunction telHref(phone: string): string {\n return `tel:${phone.replace(/[^\\d+]/g, \"\")}`;\n}\n\nfunction ContactMap({ url }: { url: string }): ReactElement {\n if (isAllowedEmbedSrc(url)) {\n return (\n <div className=\"rt-tpl-contact-map\">\n <iframe\n src={url}\n title=\"오시는길 지도\"\n loading=\"lazy\"\n referrerPolicy=\"no-referrer-when-downgrade\"\n />\n </div>\n );\n }\n return (\n <p className=\"rt-tpl-contact-map-fallback\">\n <a href={url} rel=\"noopener noreferrer\" target=\"_blank\">\n 길찾기\n </a>\n </p>\n );\n}\n\nexport function TemplateContact(section: ContactSection): ReactElement {\n const { heading, form, phone, address, hours, mapEmbedUrl } = section.props;\n return (\n <section\n data-roottale-tpl=\"contact\"\n data-variant={section.variant}\n id={section.id}\n className=\"rt-tpl-section rt-tpl-contact\"\n style={sectionThemeStyle(section.theme)}\n >\n <div className=\"rt-tpl-container rt-tpl-contact-inner\">\n <div className=\"rt-tpl-contact-info\">\n {heading ? <h2 className=\"rt-tpl-heading\">{heading}</h2> : null}\n {phone ? (\n <a className=\"rt-tpl-contact-phone\" href={telHref(phone)} data-track=\"cta_call\">\n {phone}\n </a>\n ) : null}\n {address ? <address className=\"rt-tpl-contact-address\">{address}</address> : null}\n {hours ? (\n <table className=\"rt-tpl-contact-hours\">\n <caption className=\"rt-tpl-sr-only\">진료시간</caption>\n <tbody>\n {hours.map((row) => (\n <tr key={`${row.day}-${row.open}-${row.close}`}>\n <th scope=\"row\">{row.day}</th>\n <td>\n <span>{row.open}</span>\n <span aria-hidden=\"true\">-</span>\n <span>{row.close}</span>\n {row.note ? (\n <span className=\"rt-tpl-contact-hours-note\">{row.note}</span>\n ) : null}\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n ) : null}\n {mapEmbedUrl ? <ContactMap url={mapEmbedUrl} /> : null}\n </div>\n {form.enabled ? (\n <div className=\"rt-tpl-contact-form\">\n <RootTaleLeadForm action=\"/api/inquiry\" vertical={form.vertical} />\n </div>\n ) : null}\n </div>\n </section>\n );\n}\n","import type { ReactElement, ReactNode } from \"react\";\nimport type { CmsPostContent } from \"@roottale/cms-client/server\";\n\nimport { RootTaleLeadForm } from \"./LeadForm.js\";\nimport {\n SectionSchema,\n type BlogListSection,\n type ContactSection,\n type CtaBannerSection,\n type FaqSection,\n type HeroSection,\n type LeadFormSection,\n type RichTextSection,\n type ServicesSection,\n type TrustSection,\n} from \"./templates/site-config.js\";\nimport {\n TemplateContact,\n TemplateCtaBanner,\n TemplateFaq,\n TemplateHero,\n TemplateRichText,\n TemplateServices,\n TemplateTrust,\n sectionThemeStyle,\n} from \"./templates/sections.js\";\n\ntype TiptapNode = {\n type?: string;\n attrs?: Record<string, unknown>;\n};\n\nexport type CmsBoundContext = { apiKey: string; baseUrl?: string };\n\ntype BlogListProps = {\n apiKey: string;\n baseUrl?: string;\n limit?: number;\n emptyMessage?: string;\n postHref?: (post: CmsPostContent) => string;\n};\n\nexport type SectionRenderContext = {\n cms: CmsBoundContext | null;\n renderBlogList: (props: BlogListProps) => ReactNode;\n isDev: () => boolean;\n};\n\nfunction warnSkippedCmsBound(sectionType: string, isDev: () => boolean): void {\n if (isDev()) {\n console.warn(\n `[cms-renderer-next] section 노드 sectionType=${sectionType} 는 apiKey 컨텍스트가 없어 렌더 skip`,\n );\n }\n}\n\nexport function renderSectionNode(\n node: TiptapNode,\n key: number,\n context: SectionRenderContext,\n): ReactElement | null {\n const attrs = node.attrs ?? {};\n const input = {\n type: typeof attrs.sectionType === \"string\" ? attrs.sectionType : \"\",\n variant: attrs.variant ?? undefined,\n theme: attrs.theme ?? undefined,\n props: attrs.props ?? {},\n };\n const parsed = SectionSchema.safeParse(input);\n if (!parsed.success) {\n if (context.isDev()) {\n console.warn(\n `[cms-renderer-next] section 노드 parse 실패 (sectionType=${input.type}) — 렌더 skip:`,\n parsed.error.issues,\n );\n }\n return null;\n }\n const section = parsed.data;\n switch (section.type) {\n case \"hero\":\n return <TemplateHero key={key} {...(section as HeroSection)} />;\n case \"rich-text\":\n return <TemplateRichText key={key} {...(section as RichTextSection)} />;\n case \"services\":\n return <TemplateServices key={key} {...(section as ServicesSection)} />;\n case \"faq\":\n return <TemplateFaq key={key} {...(section as FaqSection)} />;\n case \"cta-banner\":\n return <TemplateCtaBanner key={key} {...(section as CtaBannerSection)} />;\n case \"trust\":\n return <TemplateTrust key={key} {...(section as TrustSection)} />;\n case \"contact\":\n if (!context.cms) {\n warnSkippedCmsBound(section.type, context.isDev);\n return null;\n }\n return <TemplateContact key={key} {...(section as ContactSection)} />;\n case \"blog-list\": {\n if (!context.cms) {\n warnSkippedCmsBound(section.type, context.isDev);\n return null;\n }\n const s = section as BlogListSection;\n const base = s.props.blogBasePath ?? \"/blog\";\n return (\n <section\n key={key}\n data-roottale-tpl=\"blog-list\"\n data-roottale-cms=\"list\"\n id={s.id}\n className=\"rt-tpl-section rt-tpl-blog-list\"\n style={sectionThemeStyle(s.theme)}\n >\n <div className=\"rt-tpl-container\">\n {s.props.heading ? (\n <h2 className=\"rt-tpl-heading\">{s.props.heading}</h2>\n ) : null}\n {context.renderBlogList({\n apiKey: context.cms.apiKey,\n baseUrl: context.cms.baseUrl,\n limit: s.props.limit,\n emptyMessage: s.props.emptyMessage,\n postHref: (post) => `${base}/${post.slug}`,\n })}\n </div>\n </section>\n );\n }\n case \"lead-form\": {\n if (!context.cms) {\n warnSkippedCmsBound(section.type, context.isDev);\n return null;\n }\n const s = section as LeadFormSection;\n return (\n <section\n key={key}\n data-roottale-tpl=\"lead-form\"\n data-roottale-cms=\"lead-form\"\n id={s.id}\n className=\"rt-tpl-section rt-tpl-lead-form\"\n style={sectionThemeStyle(s.theme)}\n >\n <div className=\"rt-tpl-container\">\n <RootTaleLeadForm\n action={s.props.action}\n vertical={s.props.vertical}\n heading={s.props.heading}\n description={s.props.description}\n submitLabel={s.props.submitLabel}\n />\n </div>\n </section>\n );\n }\n default:\n if (context.isDev()) {\n console.warn(\n `[cms-renderer-next] section 노드 sectionType=${section.type} 는 renderNode 위임 대상이 아님(cms-bound/미지) — skip`,\n );\n }\n return null;\n }\n}\n","/**\n * `<RootTalePostCard>` — server-only summary card for a single CmsPostContent.\n *\n * Building block for `<RootTaleBlogList>` (and customer-side custom grids).\n * Pure SSR — featured image + category + title + meta + excerpt. Featured\n * image uses `CmsPostContent.featuredImageUrl` (CF Images delivery URL exposed\n * since cms-client 0.12); category is the first `terms` entry whose taxonomy is\n * `category`. Both render only when present, so text-only posts are unchanged.\n */\n\nimport type { ReactElement } from \"react\";\nimport type { CmsPostContent, CmsTermRef } from \"@roottale/cms-client/server\";\n\ntype CmsPostWithAuthor = CmsPostContent & { authorName?: string | null };\n\nexport interface RootTalePostCardProps {\n post: CmsPostContent;\n /** Customer-side URL builder. Defaults to `/blog/${slug}`. */\n href?: (post: CmsPostContent) => string;\n /**\n * 표시 옵션 (플랫폼 blog-settings + 글별 override resolve 결과). 미지정 시\n * 발행일만 표시(작성자 없음) — 직접 사용하는 커스텀 그리드 하위호환.\n * `showImage`/`showCategory` 는 데이터가 있을 때 노출 여부(기본 노출).\n */\n display?: {\n showDate: boolean;\n showAuthor: boolean;\n showImage?: boolean;\n showCategory?: boolean;\n };\n}\n\nfunction firstCategoryName(terms: CmsTermRef[] | undefined | null): string | null {\n if (!terms) return null;\n const cat = terms.find((t) => t.taxonomy === \"category\");\n return cat ? cat.name : null;\n}\n\nexport function RootTalePostCard(props: RootTalePostCardProps): ReactElement {\n const { post, href = defaultHref, display } = props;\n const showDate = display ? display.showDate : true;\n const showAuthor = display ? display.showAuthor : false;\n const showImage = display?.showImage !== false;\n const showCategory = display?.showCategory !== false;\n const authorName = (post as CmsPostWithAuthor).authorName;\n const publishedDate = showDate ? formatPublishedDate(post.publishedAt) : \"\";\n const views = typeof post.viewCount === \"number\" ? post.viewCount : null;\n const imageUrl = showImage ? post.featuredImageUrl : null;\n const category = showCategory ? firstCategoryName(post.terms) : null;\n return (\n <article data-roottale-cms=\"card\" className=\"rt-cms-card\">\n <a className=\"rt-cms-card-link\" href={href(post)}>\n {imageUrl ? (\n // eslint-disable-next-line @next/next/no-img-element -- SSR string URL, framework-agnostic\n <img\n className=\"rt-cms-card-image\"\n src={imageUrl}\n alt=\"\"\n loading=\"lazy\"\n decoding=\"async\"\n />\n ) : null}\n {category ? (\n <span className=\"rt-cms-card-category\">{category}</span>\n ) : null}\n {publishedDate || (showAuthor && authorName) || views !== null ? (\n <p className=\"rt-cms-meta\">\n {publishedDate ? (\n <span className=\"rt-cms-meta-item\" data-rt-cms-meta=\"date\">\n {publishedDate}\n </span>\n ) : null}\n {showAuthor && authorName ? (\n <span className=\"rt-cms-meta-item\" data-rt-cms-meta=\"author\">\n 작성자 {authorName}\n </span>\n ) : null}\n {/* ADR-0065 — 조회수는 사이트 공개 토글 on 일 때만 API 가 viewCount 를\n 내려준다. 따라서 값이 있으면 그대로 노출(별도 표시 설정 불필요). */}\n {views !== null ? (\n <span className=\"rt-cms-meta-item\" data-rt-cms-meta=\"views\">\n 조회 {views.toLocaleString(\"ko-KR\")}\n </span>\n ) : null}\n </p>\n ) : null}\n <h2 className=\"rt-cms-title\">{post.title}</h2>\n {post.excerpt ? <p className=\"rt-cms-excerpt\">{post.excerpt}</p> : null}\n <span className=\"rt-cms-card-action\" aria-hidden=\"true\">\n 읽기\n </span>\n </a>\n </article>\n );\n}\n\nfunction defaultHref(post: CmsPostContent): string {\n return `/blog/${post.slug}`;\n}\n\nfunction formatPublishedDate(iso: string): string {\n const d = new Date(iso);\n if (Number.isNaN(d.getTime())) return \"\";\n return new Intl.DateTimeFormat(\"ko-KR\", {\n year: \"numeric\",\n month: \"2-digit\",\n day: \"2-digit\",\n }).format(d);\n}\n","/**\n * `<RootTaleTableOfContents>` — server-only anchor list for blog posts.\n *\n * Pure SSR. Renders `<nav>` + ordered anchors that link to `#<id>` slots\n * emitted by `attachHeadingIds`. No scroll-spy here — that requires an\n * `IntersectionObserver` which only exists in the browser; a client-island\n * variant can layer on top later without changing the data contract.\n *\n * Internal ancestor: `roottale-internal/packages/cms-renderer/src/TableOfContents.tsx`\n * (had `'use client'` + IntersectionObserver). Stripped to server-safe form per\n * the platform renderer contract (no `'use client'` boundary — ADR-0023 §5.3).\n */\n\nimport type { ReactElement } from \"react\";\nimport type { TocEntry } from \"./toc.js\";\n\nexport interface RootTaleTableOfContentsProps {\n entries: TocEntry[];\n title?: string;\n}\n\nexport function RootTaleTableOfContents(\n props: RootTaleTableOfContentsProps,\n): ReactElement | null {\n const { entries, title = \"목차\" } = props;\n if (entries.length === 0) return null;\n return (\n <nav data-roottale-cms=\"toc\" className=\"rt-cms-toc\" aria-label={title}>\n <details className=\"rt-cms-toc-details\" open>\n <summary className=\"rt-cms-toc-summary\">\n {title ? <span className=\"rt-cms-toc-title\">{title}</span> : null}\n <span className=\"rt-cms-toc-toggle\" aria-hidden=\"true\" />\n </summary>\n <ol className=\"rt-cms-toc-list\">\n {entries.map((entry) => (\n <li\n key={entry.id}\n className={`rt-cms-toc-item rt-cms-toc-level-${entry.level}`}\n >\n <a href={`#${entry.id}`}>{entry.text}</a>\n </li>\n ))}\n </ol>\n </details>\n </nav>\n );\n}\n","import type { CSSProperties } from \"react\";\nimport type { RootTaleTheme } from \"@roottale/cms-client/server\";\n\nexport type RootTaleCssVars = CSSProperties & Record<`--${string}`, string>;\n\nexport function themeToCssVars(theme: RootTaleTheme | null): RootTaleCssVars | null {\n if (!theme) return null;\n const vars: RootTaleCssVars = {};\n const c = theme.colors ?? {};\n if (c.primary) vars[\"--rt-color-primary\"] = c.primary;\n if (c.primaryForeground) vars[\"--rt-color-primary-foreground\"] = c.primaryForeground;\n if (c.foreground) vars[\"--rt-color-foreground\"] = c.foreground;\n if (c.background) vars[\"--rt-color-background\"] = c.background;\n if (c.secondary) vars[\"--rt-color-secondary\"] = c.secondary;\n if (c.secondaryForeground) vars[\"--rt-color-secondary-foreground\"] = c.secondaryForeground;\n if (c.accent) vars[\"--rt-color-accent\"] = c.accent;\n if (c.accentForeground) vars[\"--rt-color-accent-foreground\"] = c.accentForeground;\n if (c.muted) vars[\"--rt-color-muted\"] = c.muted;\n if (c.mutedForeground) vars[\"--rt-color-muted-foreground\"] = c.mutedForeground;\n if (c.destructive) vars[\"--rt-color-destructive\"] = c.destructive;\n if (c.destructiveForeground) vars[\"--rt-color-destructive-foreground\"] = c.destructiveForeground;\n if (c.border) vars[\"--rt-color-border\"] = c.border;\n if (c.input) vars[\"--rt-color-input\"] = c.input;\n if (c.ring) vars[\"--rt-color-ring\"] = c.ring;\n const f = theme.fonts ?? {};\n if (f.body) vars[\"--rt-font-body\"] = f.body;\n if (f.display) vars[\"--rt-font-display\"] = f.display;\n const r = theme.radius ?? {};\n if (r.sm) vars[\"--rt-radius-sm\"] = r.sm;\n if (r.md) vars[\"--rt-radius-md\"] = r.md;\n if (r.lg) vars[\"--rt-radius-lg\"] = r.lg;\n if (r.xl) vars[\"--rt-radius-xl\"] = r.xl;\n if (Object.keys(vars).length === 0) return null;\n return vars;\n}\n","/**\n * `@roottale/cms-renderer/toc` — Tiptap doc → TOC entries + heading id injection.\n *\n * Server-only utility. Walks a Tiptap JSON doc, extracts H1-H4 headings, and\n * derives stable kebab-case ids (Korean-safe). The companion `attachHeadingIds`\n * mutates a shallow clone of the doc so renderers can emit heading anchors\n * that match what `<RootTaleTableOfContents>` links to.\n *\n * Internal ancestor: `roottale-internal/packages/cms-renderer/src/toc.ts`\n * (HTML-string based, vendored snapshot). Ported to Tiptap-JSON because the\n * platform renderer no longer round-trips through HTML before render.\n */\n\nexport interface TocEntry {\n level: 2 | 3 | 4;\n text: string;\n id: string;\n}\n\ntype TiptapNode = {\n type?: string;\n text?: string;\n attrs?: Record<string, unknown>;\n content?: TiptapNode[];\n marks?: { type?: string }[];\n};\n\ntype TiptapDoc = { type?: string; content?: TiptapNode[] };\n\nfunction tocLevel(rawLevel: number): TocEntry[\"level\"] | null {\n if (rawLevel < 1 || rawLevel > 4) return null;\n if (rawLevel < 3) return 2;\n return rawLevel === 3 ? 3 : 4;\n}\n\nexport function headingToId(text: string): string {\n return text\n .trim()\n .toLowerCase()\n .replace(/[^\\p{L}\\p{N}\\s-]/gu, \"\")\n .replace(/\\s+/g, \"-\")\n .replace(/-+/g, \"-\")\n .replace(/^-|-$/g, \"\");\n}\n\nfunction textOf(node: TiptapNode): string {\n if (typeof node.text === \"string\") return node.text;\n if (!Array.isArray(node.content)) return \"\";\n return node.content.map(textOf).join(\"\");\n}\n\nexport function extractToc(doc: Record<string, unknown>): TocEntry[] {\n const root = doc as TiptapDoc;\n const content = Array.isArray(root.content) ? root.content : [];\n const entries: TocEntry[] = [];\n const idCounts = new Map<string, number>();\n\n for (const node of content) {\n if (node?.type !== \"heading\") continue;\n const rawLevel = Number(node.attrs?.level ?? 2);\n const level = tocLevel(rawLevel);\n if (level === null) continue;\n const text = textOf(node).trim();\n if (!text) continue;\n\n const base = headingToId(text) || \"heading\";\n const count = idCounts.get(base) ?? 0;\n const id = count === 0 ? base : `${base}-${count}`;\n idCounts.set(base, count + 1);\n\n entries.push({ level, text, id });\n }\n\n return entries;\n}\n\n/**\n * Returns a shallow-cloned Tiptap doc where each H1-H4 carries the matching\n * `attrs.id` from `entries` (same order as `extractToc` returns). Existing\n * `id` attrs are overwritten so renderer + TOC always agree on anchors.\n */\nexport function attachHeadingIds(\n doc: Record<string, unknown>,\n entries: TocEntry[],\n): Record<string, unknown> {\n const root = doc as TiptapDoc;\n const content = Array.isArray(root.content) ? root.content : [];\n let cursor = 0;\n const nextContent = content.map((node) => {\n if (node?.type !== \"heading\") return node;\n const rawLevel = Number(node.attrs?.level ?? 2);\n if (rawLevel < 1 || rawLevel > 4) return node;\n if (!textOf(node).trim()) return node;\n const entry = entries[cursor++];\n if (!entry) return node;\n return {\n ...node,\n attrs: { ...(node.attrs ?? {}), id: entry.id },\n };\n });\n return { ...(root as Record<string, unknown>), content: nextContent };\n}\n","/**\n * `<RootTaleFloatingCta>` — fixed-position call-to-action stack.\n *\n * Server-only (anchors only, no state). External targets (`kakao` / `custom`)\n * open in a new tab with `rel=\"noopener noreferrer\"`; tel/contact stay in the\n * same tab so iOS/Android can trigger the dialer / mailto handler.\n *\n * Internal ancestor: `roottale-internal/packages/cms-renderer/src/FloatingCta.tsx`.\n */\n\nimport type { ReactElement } from \"react\";\n\nexport type CtaButtonType = \"phone\" | \"contact\" | \"kakao\" | \"custom\";\n\nexport interface CtaButton {\n type: CtaButtonType;\n label: string;\n href: string;\n /** Optional override icon (emoji or short text). */\n icon?: string;\n}\n\nexport type FloatingCtaPosition =\n | \"bottom-right\"\n | \"bottom-left\"\n | \"bottom-center\";\n\nexport interface RootTaleFloatingCtaProps {\n buttons: CtaButton[];\n position?: FloatingCtaPosition;\n}\n\nexport function RootTaleFloatingCta(\n props: RootTaleFloatingCtaProps,\n): ReactElement | null {\n const { buttons, position = \"bottom-right\" } = props;\n if (!buttons || buttons.length === 0) return null;\n return (\n <div\n data-roottale-cms=\"floating-cta\"\n className={`rt-cms-floating-cta rt-cms-floating-cta--${position}`}\n >\n {buttons.map((btn, idx) => {\n const isExternal = btn.type === \"kakao\" || btn.type === \"custom\";\n return (\n <a\n key={`${btn.type}-${idx}`}\n href={btn.href}\n className={`rt-cms-floating-cta__btn rt-cms-floating-cta__btn--${btn.type}`}\n target={isExternal ? \"_blank\" : undefined}\n rel={isExternal ? \"noopener noreferrer\" : undefined}\n >\n <span aria-hidden=\"true\" className=\"rt-cms-floating-cta__icon\">\n {btn.icon ?? defaultIcon(btn.type)}\n </span>\n <span className=\"rt-cms-floating-cta__label\">{btn.label}</span>\n </a>\n );\n })}\n </div>\n );\n}\n\nfunction defaultIcon(type: CtaButtonType): string {\n switch (type) {\n case \"phone\":\n return \"☎\";\n case \"contact\":\n return \"✉\";\n case \"kakao\":\n return \"💬\";\n case \"custom\":\n return \"→\";\n }\n}\n","// ADR-0034 §1.5 amended — Block JSON → React elements\n//\n// Next/RSC public renderer 가 본 함수로 block tree 를 server-side React element\n// 로 변환. cms-renderer-astro 의 `block-to-html.ts` 와 1:1 대응 (같은 7 + α\n// block 핸들러). `BlockDefinition.nextRender` 정합.\n//\n// 알려지지 않은 block 은 rawHtml 출력 (codex v3 verdict #4, opaque atom\n// round-trip 보존).\n\nimport type { ReactElement, ReactNode } from \"react\";\n\nimport {\n type Block,\n type BindingResolver,\n applyBlockBindings,\n createFieldBindingResolver,\n sanitizeHtml,\n} from \"@roottale/cms-core\";\n\nfunction isString(value: unknown): value is string {\n return typeof value === \"string\";\n}\n\nfunction htmlContent(html: string): { __html: string } {\n // dangerouslySetInnerHTML 진입 전 단일 chokepoint — 모든 block 의\n // rawHtml/content 는 여기서 sanitize 된다 (cms-core sanitizeHtml). API 키로\n // 글을 작성하는 외부 writer 가 inline `<script>`/`on*`/`javascript:` 를\n // 주입하는 저장형 XSS 방어.\n return { __html: sanitizeHtml(html) };\n}\n\nfunction renderInner(blocks: readonly Block[]): ReactElement[] {\n return blocks.map((b, i) => renderBlock(b, i));\n}\n\n/**\n * 단일 block 을 ReactElement 로 변환. `block-to-html.ts` 와 동일 분기.\n */\nexport function renderBlock(block: Block, key: number = 0): ReactElement {\n const dataBlockId = block._id;\n\n switch (block.name) {\n case \"core/paragraph\": {\n const text = isString(block.attributes.content)\n ? block.attributes.content\n : block.rawHtml ?? \"\";\n return (\n <p\n key={key}\n data-block-id={dataBlockId}\n dangerouslySetInnerHTML={htmlContent(text)}\n />\n );\n }\n case \"core/heading\": {\n const levelRaw = block.attributes.level;\n const level =\n typeof levelRaw === \"number\" && levelRaw >= 2 && levelRaw <= 4\n ? levelRaw\n : 2;\n const text = isString(block.attributes.content)\n ? block.attributes.content\n : block.rawHtml ?? \"\";\n const props = {\n \"data-block-id\": dataBlockId,\n dangerouslySetInnerHTML: htmlContent(text),\n } as const;\n if (level === 2) return <h2 key={key} {...props} />;\n if (level === 3) return <h3 key={key} {...props} />;\n return <h4 key={key} {...props} />;\n }\n case \"core/image\": {\n const src = isString(block.attributes.url) ? block.attributes.url : \"\";\n const alt = isString(block.attributes.alt) ? block.attributes.alt : \"\";\n const caption = isString(block.attributes.caption)\n ? block.attributes.caption\n : null;\n return (\n <figure key={key} data-block-id={dataBlockId}>\n {/* eslint-disable-next-line @next/next/no-img-element */}\n <img src={src} alt={alt} loading=\"lazy\" />\n {caption && <figcaption>{caption}</figcaption>}\n </figure>\n );\n }\n case \"core/list\": {\n const ordered = block.attributes.ordered === true;\n const items = (block.innerBlocks ?? []).map(\n (item, i): ReactNode =>\n item.rawHtml ? (\n <li\n key={i}\n dangerouslySetInnerHTML={htmlContent(item.rawHtml)}\n />\n ) : (\n <li key={i}>{renderInner(item.innerBlocks ?? [])}</li>\n ),\n );\n return ordered ? (\n <ol key={key} data-block-id={dataBlockId}>\n {items}\n </ol>\n ) : (\n <ul key={key} data-block-id={dataBlockId}>\n {items}\n </ul>\n );\n }\n case \"core/quote\": {\n const inner = renderInner(block.innerBlocks ?? []);\n const citation = isString(block.attributes.citation)\n ? block.attributes.citation\n : null;\n return (\n <blockquote key={key} data-block-id={dataBlockId}>\n {inner}\n {citation && <cite>{citation}</cite>}\n </blockquote>\n );\n }\n case \"core/code\": {\n const code = isString(block.attributes.content)\n ? block.attributes.content\n : block.rawHtml ?? \"\";\n const lang = isString(block.attributes.language)\n ? block.attributes.language\n : undefined;\n return (\n <pre\n key={key}\n data-block-id={dataBlockId}\n data-language={lang}\n >\n <code>{code}</code>\n </pre>\n );\n }\n case \"core/columns\": {\n return (\n <div\n key={key}\n className=\"rt-columns\"\n data-block-id={dataBlockId}\n >\n {renderInner(block.innerBlocks ?? [])}\n </div>\n );\n }\n case \"core/separator\":\n return <hr key={key} data-block-id={dataBlockId} />;\n case \"core/spacer\": {\n const heightRaw = block.attributes.height;\n const height = isString(heightRaw) ? heightRaw : \"32px\";\n return (\n <div\n key={key}\n className=\"rt-spacer\"\n data-block-id={dataBlockId}\n style={{ height }}\n />\n );\n }\n case \"core/group\": {\n return (\n <div\n key={key}\n className=\"rt-group\"\n data-block-id={dataBlockId}\n >\n {renderInner(block.innerBlocks ?? [])}\n </div>\n );\n }\n default: {\n // codex review §15 — 알려지지 않은 block 의 rawHtml 을 그대로\n // `dangerouslySetInnerHTML` 로 내보내면 API key 로 글을 작성하는 외부\n // 클라이언트가 임의 HTML/script 를 외부 사이트로 삽입할 수 있다 (XSS).\n // rawHtml 분기 자체를 제거 — 후속 PR 에서 cms-core 의 sanitize 모듈을\n // 도입한 뒤에만 raw 출력을 허용한다.\n const rawHtml = block.rawHtml;\n return (\n <div\n key={key}\n className=\"rt-unknown-block\"\n data-block-id={dataBlockId}\n data-block-name={block.name}\n data-rawhtml-suppressed={rawHtml ? \"true\" : undefined}\n >\n {renderInner(block.innerBlocks ?? [])}\n </div>\n );\n }\n }\n}\n\n/** Block tree 전체를 React element 배열로 직렬화. */\nexport function renderBlocks(blocks: readonly Block[]): ReactElement[] {\n return blocks.map((b, i) => renderBlock(b, i));\n}\n\n/**\n * ADR-0041 §7 — Block Bindings 를 먼저 적용한 뒤 렌더.\n * `metadata.bindings` 의 `roottale/field`/`acf/field` 를 `fields`(acf 값)로\n * 치환. 바인딩 없으면 renderBlocks 와 동일.\n */\nexport function renderBoundBlocks(\n blocks: readonly Block[],\n opts: {\n fields?: Record<string, unknown> | null;\n resolver?: BindingResolver;\n } = {},\n): ReactElement[] {\n const resolver = opts.resolver ?? createFieldBindingResolver(opts.fields);\n return renderBlocks(applyBlockBindings(blocks, resolver));\n}\n"],"mappings":"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","names":["isAllowedEmbedSrc","Fragment","jsx","jsxs","jsx","jsxs","jsx","jsxs","jsx","jsxs","jsx","jsxs","jsx","jsxs","Fragment","jsx","jsxs","formatPublishedDate","isAllowedEmbedSrc"]}