Commit Graph

311 Commits

Author SHA1 Message Date
Haewon Kam
f1f8e6f6e4 feat: webforai.kr 공개 기능 스위치 (요금·예시 리포트·Use Cases)
- src/lib/site.ts 의 SHOW_PRICING·SHOW_SAMPLE_REPORT·SHOW_USE_CASES 로 라우트·헤더·랜딩 버튼을 한 곳에서 켜고 끈다. 공개 배포 기본값은 모두 꺼짐
- 리포트 본문을 DiscoveryReportView 로 분리하고, 가상 데이터만 그리는 /discovery/sample 을 만든다
- 예시 리포트 경로는 병원 작업 공간 메뉴 대신 일반 헤더를 쓴다
- Use Cases 데이터(src/data/useCases.ts, 비어 있음)와 섹션을 두고, 사례가 있을 때만 섹션·메뉴가 보인다

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 14:21:53 +09:00
Haewon Kam
a47b792912 fix: 로그인 제출 안내에서 '준비 중' 문장을 뺀다
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 13:16:13 +09:00
Haewon Kam
d4af2bf0d9 fix: 하단 CTA 문구를 AI 조작으로 읽히지 않게 고치고, 로그인 안내에서 '준비 중' 문장을 뺀다
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 13:13:30 +09:00
Haewon Kam
ee904c816b fix: 랜딩 하단 CTA 호칭을 '당신'에서 '여러분'으로
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 13:10:21 +09:00
Haewon Kam
85d09819c8 feat: webforai.kr 에서 요금(Pricing) 페이지를 숨긴다
요금이 확정 전이라 공개 배포에서는 헤더 메뉴에서 빼고 /pricing 은 홈으로 돌린다.
페이지 코드가 번들에서도 빠진다. infinith-demo 배포는 그대로다.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 13:08:37 +09:00
Haewon Kam
4245959a0f docs: anon 키로 실제 병원 데이터를 읽는 RLS 정책 정리 초안 (실행 안 함)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 12:11:13 +09:00
Haewon Kam
c2e7d5adfc fix: webforai.kr 번들에서 Supabase 접속 정보를 뺀다
lib/supabase.ts 의 최상단 createClient() 가 부수효과로 남아, 쓰는 곳이 없는 공개 배포에도
Supabase URL·anon 키가 실렸다. 이 키로 marketing_reports 등 실제 병원 데이터 테이블을 읽을 수 있었다.
supabase 모듈을 부수효과 없는 모듈로 표시해 쓰지 않으면 번들에서 빠지게 한다. infinith-demo 빌드는 그대로 쓴다.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 12:06:28 +09:00
Haewon Kam
b4d983a7d4 fix: webforai.kr 빌드 결과에서 실제 병원 리포트·스크린샷 파일을 뺀다
public/ 은 infinith-demo 와 공용이라 reports/viewclinic(영문 진단 리포트 HTML·PDF)과
assets/clients/(병원 채널 스크린샷)가 공개 배포에도 올라가 주소만 알면 열렸다.
discovery 빌드의 writeBundle 에서 두 폴더를 지운다. infinith-demo 빌드는 그대로다.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 12:04:29 +09:00
Haewon Kam
c90aef654f feat: webforai.kr 방문 측정 (GTM·GA4) 연결과 처리방침 반영
- discovery 빌드에만 GTM(GTM-WQNTZVZH) 스니펫을 넣는다. GA4(G-013Z3BFY35)와 키 이벤트는 컨테이너 안에서 설정한다
- 문의 접수 성공 시 dataLayer 에 generate_lead(lead_source) 를 보낸다. 개인정보는 넣지 않는다
- GTM 가져오기 파일(docs/analytics/gtm_webforai_import.json): GA4 기본 태그, generate_lead·click_tel·click_email
- 개인정보처리방침 제1·5·8조에 Google 애널리틱스 분석 쿠키, 수집 항목, 국외 이전, 거부 방법을 반영한다

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 11:40:31 +09:00
Haewon Kam
f03c6a5b90 feat: webforai.kr 공개 전 정리 (비식별 샘플·실제 리포트 비공개·약관)
- 랜딩 샘플 리포트·AI 답변 예시·문의 서식 예시를 가상 A성형외과로 바꾼다 (discovery_sample, 고정 시드 무작위 판정)
- VITE_SITE=discovery 배포는 랜딩·요금·로그인·약관만 연다. 실제 병원 진단 결과와 내부 화면 경로는 홈으로 돌린다
- src/data·src/services 를 부수효과 없는 모듈로 표시해 쓰지 않는 실제 병원 목업이 공개 번들에서 빠지게 한다
- 공개 배포 번들에 GEMINI_API_KEY 를 넣지 않는다
- 헤더 Product·Use Cases 는 공개 배포에서 숨기고, 푸터에 개인정보처리방침·이용약관 페이지를 연결한다
- 기준표 설명의 실제 병원 예시 문구를 일반 예시로 바꾼다

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 11:23:41 +09:00
Haewon Kam
63e71b73cc feat: 진단 요청 버튼을 문의 폼으로 연결하고 샘플 리포트 버튼을 뺀다
- "URL 입력으로 시작하기" → "AEO, GEO 진단 요청하기". 문의하기와 같은 ContactModal 을 열어
  구글시트 기록·메일 알림으로 받는다 (source: discovery_hero / discovery_cta)
- 공개 폼에서 supporter_builds 큐에 자동 빌드를 넣던 경로를 끊는다
- 히어로의 "샘플 리포트 보기" 버튼 제거 (실제 병원 리포트로 연결되던 링크)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 11:10:24 +09:00
Haewon Kam
00b8339ca2 feat(contact): 문의 전송을 서버 함수로 옮겨 토큰 노출 제거
브라우저로 내려간 값은 무엇이든 공개다. VITE_LEAD_TOKEN 이 번들에 평문으로
박혀 있었고(실제로 추출해 확인했다) 토큰은 보호 장치가 되지 못했다. 토큰을
새로 발급해도 똑같이 공개된다.

- api/lead.js 신규. 토큰과 Apps Script 주소를 서버 환경변수(LEAD_TOKEN·
  LEAD_ENDPOINT)로만 들고 요청을 전달한다. VITE_ 접두사를 쓰지 않아 번들에
  들어가지 않는다.
- Origin 허용 목록으로 webforai.kr·www·aio2o.webforai.kr 만 받는다. 토큰과
  달리 Origin 은 브라우저가 채우고 스크립트가 바꿀 수 없다. 다른 사이트에
  폼을 심어 이 주소를 때리는 것을 막는다. aio2o 는 정적 배포라 자체 함수를
  둘 수 없어 이 함수를 같이 쓴다(교차 출처라 CORS 를 명시).
- submitLead 는 같은 출처 /api/lead 로 보낸다. 클라이언트 환경변수가 없어졌다.
- vercel.json 의 전체 경로 리라이트에서 /api 를 제외한다. 안 그러면 함수
  요청까지 index.html 로 삼킨다.

Vercel 환경변수: LEAD_ENDPOINT·LEAD_TOKEN 추가, VITE_LEAD_* 2개 삭제.
Vite 는 코드에서 안 쓰더라도 VITE_ 접두사 변수를 전부 번들에 넣기 때문에
삭제까지 해야 토큰이 사라진다(삭제 전 재배포에서 여전히 검출됐다).

검증: 운영 번들에서 토큰 0건·script.google.com 0건. /api/lead 정상 응답
{"ok":true,"mailed":true}. 다른 Origin 403. SPA 라우트(/discovery/:id·
/actions/:id) 200 유지. 실제 폼 제출 접수 확인.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-10-06 16:56:15 +09:00
Haewon Kam
e0f1d4df96 fix(footer): 팩스 번호 제거
haewon 2026-10-01 지시로 두 사이트에서 팩스 0504-335-6463 을 뺀다.
aio2o.webforai.kr 쪽도 같은 날 함께 제거했다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-10-01 13:48:28 +09:00
Haewon Kam
9eb7509bc8 fix(contact): 문의 모달을 다시 열면 접수 화면이 남아 있던 문제
한 번 문의를 보낸 뒤 닫았다가 다시 열면 폼 대신 "문의가 접수되었습니다"
화면이 그대로 떠서 두 번째 문의를 할 수 없었다. 모달을 닫아도 컴포넌트가
언마운트되지 않아 state 가 'done' 으로 남아 있던 탓이다.

열릴 때마다 입력값과 상태를 비운다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-10-01 13:18:00 +09:00
Haewon Kam
8a96aa0e97 feat(contact): 푸터에 사업자 정보 추가, 문의 폼에 허니팟·속도 제한
푸터 (haewon 2026-10-01)
- aio2o.webforai.kr 푸터와 같은 내용으로 맞춘다: 법인명·본사·연구소 주소와
  전화·팩스·이메일. 값의 원본은 그쪽 data/facts.json 이며 지어내지 않았다.
- 전화 표기를 점에서 하이픈으로 바꿔 두 사이트를 같게 한다.

문의 폼 보호 장치
- 허니팟: 사람 눈에 안 보이는 입력칸(website)을 두고, 값이 차 있으면 스크립트가
  저장하지 않는다. display:none 을 건너뛰는 봇이 있어 화면 밖으로 밀어내고
  aria-hidden·tabIndex -1 로 보조기기와 탭 이동에서는 뺀다.
- 속도 제한: Apps Script 가 한 시간 30건까지만 받는다. VITE_LEAD_TOKEN 은 빌드
  시점에 번들로 들어가 공개값이라 보호 장치가 못 된다. 주소와 토큰이 알려져도
  피해가 상한 안에 묶이게 한다.
- 알림 실패 분리: 메일 발송이 실패해도 행은 남기고 접수로 응답한다. 지금까지는
  메일이 막히면(한도 소진·구글 장애) 저장됐는데도 고객 화면에 "전송 실패"가 떠
  접수된 줄 모르고 돌아가게 되어 있었다. 시트에 '알림' 열을 추가해 '보냄' /
  '실패(메일만)' 을 적어 메일이 안 간 문의를 찾을 수 있게 한다.

토큰 실값은 저장소에 넣지 않는다. 배포본 Apps Script 와 Vercel 환경변수에만 둔다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-10-01 11:53:06 +09:00
Haewon Kam
1710437874 feat(footer): 연락처 배치를 aio2o.webforai.kr 푸터와 같은 구조로
haewon 지정. 1행(로고 · 저작권/태그라인 · 메뉴) → 구분선 → 연락처 한 줄.

- 문의하기를 연락처 줄 오른쪽 버튼에서 1행 메뉴로 옮긴다. aio2o 푸터가
  회사 소개·솔루션과 같은 줄에 문의하기를 두는 것과 맞춘다.
- 연락처는 "전화 … · 이메일 …" 한 줄로 왼쪽 정렬하고 값에만 밑줄을 준다.
- 사업자명·주소는 넣지 않는다. 확인된 값이 없어 지어내지 않는다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-10-01 11:28:44 +09:00
Haewon Kam
48e0c46bc8 fix(contact): 문의 모달이 헤더 안에 갇혀 잘리던 문제
Navbar 는 backdrop-blur-md 를 쓴다. backdrop-filter 가 걸린 요소는
position:fixed 자손의 기준 블록이 되므로, 모달 오버레이의 inset-0 이
뷰포트가 아니라 nav 박스(1396x80)로 잡혔다. 그 결과 모달이 헤더 높이
안에서 잘려 페이지 콘텐츠가 위를 덮는 것처럼 보였다.

오버레이를 document.body 로 포털한다. 조상의 기준 블록·쌓임 맥락에서
완전히 벗어나므로 푸터 등 다른 위치에서 열어도 같은 문제가 생기지 않는다.

실측: 수정 전 오버레이 높이 80px → 수정 후 893px(뷰포트와 일치),
화면 중앙 좌표가 모달 내부로 잡히는 것 확인.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-10-01 10:48:15 +09:00
Haewon Kam
9c47df561b feat(contact): 푸터 연락처와 문의 폼 추가, 구글시트 적재·메일 알림 연동
연락처 통일
- 사이트 안에 이메일이 contact@o2o.kr · o2oteam@o2o.kr 두 갈래로 갈라져
  있던 것을 sales@o2o.kr 하나로 통일 (lib/contact.ts 단일 출처)
- 전화번호 010.2755.6463 을 처음으로 명시. discoveryContact.ts 는
  기존 import 경로를 깨지 않도록 재내보내기만 한다

푸터·문의 폼
- 푸터에 전화·이메일과 문의하기 버튼 추가 (사업자명·주소는 확인된 값이
  없어 넣지 않는다)
- ContactModal 신규: 성함·회사·전화·이메일·문의내용. 화면 구조는 기존
  DiscoveryLeadModal 을 그대로 따라 모달이 두 종류로 보이지 않게 한다
- Navbar 문의하기를 mailto 에서 모달로 교체. 메일 앱이 없는 사용자가
  mailto 에서 이탈하던 것을 막는다

수신 경로
- submitLead: Apps Script 웹앱으로 POST. 이 사이트는 서버 없는 SPA 라
  중간 서버를 두지 않았다. Apps Script 는 CORS 프리플라이트에 응답하지
  않아 Content-Type 을 text/plain 으로 보내 simple request 로 맞춘다
- scripts/apps-script/lead-intake.gs: 구글시트 행 추가와 알림 메일을
  한 실행에서 처리. 시트 트리거는 스크립트가 쓴 변경에 늦거나 누락되는
  경우가 있어 doPost 안에서 바로 보낸다
- 엔드포인트 미설정·전송 실패 시 접수된 것처럼 보이지 않게 하고 메일
  폴백을 띄운다 (기존 DiscoveryLeadModal 과 같은 규칙)

토큰 실값은 저장소에 넣지 않는다. 배포본 Apps Script 와 Vercel
환경변수(VITE_LEAD_ENDPOINT · VITE_LEAD_TOKEN)에만 둔다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-10-01 10:21:09 +09:00
Haewon Kam
c0804dd2bd feat(supporters): 서포터즈 사이트 초안 표시를 눈에 띄게 강화
기존 상단 띠 배너("샘플 사이트입니다")는 스크롤하면 사라져 병원이
둘러보는 동안에는 초안인지 최종본인지 판단이 서지 않았다.

- sticky 헤더 안에 "초안" 배지 추가(스크롤해도 계속 보임), 영문판도 동일
- 문구를 "샘플 사이트입니다"에서 상태 중심으로 변경:
  "초안입니다. 검색에 노출되지 않으며, 병원 확인 전입니다."
- 띠 배너 글자 크기·굵기 상향(0.83rem/500 → 0.95rem/600)
- 인쇄 시 배지 숨김(기존 배너 규칙과 동일하게)
- INDEXABLE=true(공개본)에서는 배너·배지 모두 그대로 뜨지 않음

templates/supporters-astro(정본)와 supporters/(현재 작업 사본) 양쪽에
동일하게 반영. 이미 배포된 서포터즈 사이트(예: supporters-jkplastic)에는
재빌드·재배포 전까지 적용되지 않는다.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-21 10:27:26 +09:00
Haewon Kam
890da0ecdf feat(discovery): 진단 리포트에서 개선 제안(Action Plan) 단계 분리
증거(진단)와 처방(할 일)을 한 페이지에 두면 스크롤이 너무 길어져
둘 다 읽기 힘들다는 지적에 따라 단계를 나눴다.

- /actions/:id 신규: Action Plan(P0~P2) + What We Don't Sell + 빌드 제안
  (ActionPlanSection·IntegritySection 신규 컴포넌트, Tag는 두 페이지가
  같이 써서 공용 컴포넌트로 뺌)
- /discovery/🆔 위 섹션 제거, 진단(점수·근거·Scorecard·Rubric)만 남김.
  페이지 맨 아래 중복으로 박혀 있던 확인 항목 패널(ClinicInputsPanel)도
  제거 — /supporters/:id 전용 단계가 이미 있어 두 번 보여줄 필요가 없었다
- 진단 데이터가 없는 병원이 /actions/:id 로 오면 /discovery/:id 로 되돌림
- ClinicNav: 5단계로 확장(진단 리포트→개선 제안→사이트 빌드→사진 확인→
  확인 항목), 다음 단계 플로팅 버튼 추가(기존엔 이전 단계만 있었다)
- App.tsx: 라우트 전환 시 스크롤을 맨 위로 되돌림. 스크롤을 내린 채
  다음 단계를 누르면 새 페이지가 같은 위치(짧으면 빈 하단)에서 시작하던
  문제 수정. 인앱 해시 앵커(#breakdown)가 있으면 건드리지 않음
- 사이트 빌드/확인 항목 화면의 미리보기 버튼을 "초안 사이트 열기"로
  통일(공개 후에는 "공개본 사이트 열기")

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-21 10:27:12 +09:00
Haewon Kam
36731d3ad1 feat(discovery): JK성형외과 영문 기준 AEO/GEO 진단 추가
- 영문 사이트(jkplastic.com/en) 기준 v1.0 45/C, AEO 40/D, GEO 27/D
- audit_aeo_geo.py 자동 채점(21항목) + 영문 페이지 11개 원본 실측 보정
  - 자동 채점의 국문 전제 정규식이 놓친 B4(NAP)·C4(수치)·F1(alt) 보정
  - 외부 실측 가능한 C1·C4·C7·C8·D1·D3 확정, 오너 계정 필요한
    A6·D2·D4·E1~E4는 미검증으로 분모 제외
- discoveryResults.ts 레지스트리에 jkplastic 등록

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-21 10:26:51 +09:00
Haewon Kam
11c4bb24b3 docs: 2026-09-18 핸드오버와 다음 세션 프롬프트
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 17:27:50 +09:00
Haewon Kam
f38c0f3bda feat(front): 진단 요약과 보완 계획을 보여 준 뒤 빌드하게 한다
버튼만 두면 무엇에 동의하는지 모른 채 누르게 된다. 진단을 요약하고, 이 빌드가 무엇을
보완하는지 말한 뒤 누르게 한다.

처음 만든 판은 실행 항목을 카테고리로 갈라 "이 빌드로 채워지는 항목 5건"이라고 했는데
그 5건이 깨진 JSON-LD 수정·GBP 전화번호 등록·H1 재설계처럼 전부 병원 홈페이지 몫이었다.
카테고리는 무엇에 관한 문제인지를 말할 뿐 누가 고치는지를 말하지 않는다. 지키지 못할
약속이라 프레임을 바꿨다.

빌드는 홈페이지를 한 줄도 고치지 않는다. AI가 인용할 새 표면을 하나 더 만들어 약한
카테고리(콘텐츠·엔티티·표면)를 우회 보완할 뿐이다. 화면도 그렇게 말한다.
"이 빌드가 대신할 수 없는 것"으로 실행 항목이 그대로 병원 몫임을 밝힌다.

- 다크 섹션 위 주 버튼을 .btn-on-dark 로 뺀다. 기본 CTA 그라디언트(#4F1DA1→#021341)는
  끝 색이 배경(#0A1128)과 거의 같아 버튼으로 읽히지 않았다. 밝은 보라와 글로우로 떼어 놓는다.
- 문구에서 "결과"를 뺀다. 빌드가 끝나도 사진 확인과 확인 항목이 남아 최종이 아니고,
  누른 적 없는 사람에게 "결과 보기"부터 보이는 것도 맞지 않는다.
- 이전 단계로 가는 플로팅 버튼을 왼쪽 아래에 고정한다. 오른쪽 아래는 주 행동 자리이고,
  좁은 화면에서는 상단 절차 메뉴가 가로 스크롤 안으로 밀린다. 첫 단계에서는 띄우지 않는다.
- 떠 있던 승인 바와 BuildSiteCta 는 이 섹션과 역할이 겹쳐 지운다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 17:25:50 +09:00
Haewon Kam
14c4b82155 refactor(design): 요청문과 설명문을 텍스트 역할 토큰으로 가른다
"한 장씩 봐 주세요" 같은 요청문이 읽고 넘어가는 설명문과 같은 무게로 놓여 있어
지나치기 쉬웠다. 역할별 토큰과 클래스를 두고 화면에 적용한다.

  .ui-ask   요청문. 사람이 무엇을 해야 하는지 말하는 문장 (17px·600·인디고)
  .ui-ask-block  문단으로 서는 요청문. 왼쪽 규칙선으로 설명과 구분한다
  .ui-body  설명 본문 (16px·slate-600)
  .ui-note  보조·출처 (14px·slate-400)

크기는 기존 화면을 세어 맞췄다. text-base 167회·text-sm 58회가 지배적이라
body 를 16px, note 를 14px 로 두면 치환해도 크기가 변하지 않고 역할과 색만 정리된다.

한글 줄바꿈: word-break keep-all 로 어절을 지키고, 긴 URL·영문 토큰만
overflow-wrap anywhere 로 자른다. text-wrap 으로 마지막 줄에 한 어절만 남는 것을
줄이고, 62ch 로 줄 길이를 묶는다. 제목에도 keep-all 과 balance 를 건다.

다크 섹션은 .on-dark 로 값을 뒤집고, 그 안의 흰 카드는 .on-light 로 되돌린다.
이걸 두지 않으면 흰 글씨가 흰 배경에 얹혀 보이지 않는다(디자인 시스템 기록된 실수).

사진 확인 화면의 썸네일 줄이 누를 수 있는 것으로 안 읽혀 같이 고친다. 72px 카드,
호버 반응, 순번 배지, 현재 위치 표시, 안 정한 것 아래 띠, 범례, 이전·다음 이동을 둔다.
상태가 안 보이는 것이 크기보다 큰 문제였다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 16:56:52 +09:00
Haewon Kam
be1ea72fcc feat(front): 진단 리포트에서 사이트 빌드로 이어지는 화면
예전 전략마케팅 툴은 분석 진행 화면에서 단계를 보여 주고 리포트로 넘어갔다.
지금 버전은 진단과 사이트 빌드가 서로 모르는 상태로 떨어져 있어, 진단을 읽은 병원이
다음에 무엇을 하는지 알 수 없었다. 진단 → 빌드 → 사진 확인 → 확인 항목을 한 줄로 잇는다.

- /build/:id 를 추가한다. 예전 /report/loading 의 단계 UX(스피너·체크·진행바)를 잇되,
  진행 상황을 프런트가 만들지 않고 supporter_builds 를 폴링해 워커가 쓴 값을 읽는다.
  프런트가 파이프라인을 돌리지 않으므로 창을 닫아도 빌드는 계속되고, 재개 로직이 필요 없다.
- 워커 17단계를 고객이 읽을 수 있는 5묶음으로 접는다(src/lib/buildPhases.ts). 진행률은
  묶음이 아니라 끝난 워커 단계 수로 세어 한 묶음 안에서도 바가 움직인다.
- 경고로 끝난 단계는 완료 화면에 "확인 대기로 발행된 부분"으로 남긴다. 자동으로 못 채운
  값을 감추지 않는다. 멈춤은 발행 게이트와 실행 실패를 구분해 문구를 나눈다.
- 진단 리포트의 Action Plan 뒤에 빌드 구간을 둔다. 무엇을 만들어 주는지와 버튼만 둔다.
  버튼은 supporter_builds 에 queued 를 넣고 폴러가 집어 간다.
- 상단 메뉴를 경로로 가른다. 병원 작업 경로에서는 마케팅 메뉴 대신 4단계 절차를 띄운다.
  Pricing·Use Cases 는 사진을 고르는 중인 병원에게 방해다.
- 진단 데이터가 없는 병원(빌드만 한 곳)은 막다른 길 대신 빌드·사진 확인으로 잇는다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 16:56:34 +09:00
Haewon Kam
74c70d656e docs(design): 신청 접수 알림과 완료 안내 메일 설계 v0.1
랜딩 신청 모달은 남긴 연락처로 안내한다고 말하는데 보내는 코드가 없다.
운영자도 신청이 들어온 사실을 알 방법이 없다. discovery_leads 는 anon insert 만
열려 있고 폴러는 supporter_builds 만 본다.

메일을 셋으로 나눈다. 접수 확인(신청자)과 신규 신청 알림(운영자)은 insert 트리거로
자동 보내고, 완료 안내는 사람이 보낸다. 자동으로 보내면 사진 검토를 건너뛴 링크가
나간다. 발송은 Edge Function 에 두고 프런트에서 부르지 않는다(anon 키 스팸 경로).
email_log 로 중복 발송을 막고 안 갔다는 문의에 답할 근거를 남긴다.
연락처가 전화번호일 수 있어 contact_kind 를 함께 저장한다.

발송 서비스 계정·발신 도메인·"리포트"의 정의는 결정 대기로 8절에 적었다.

개인 업무 문서(주간보고)는 저장소에 올리지 않는다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 16:22:28 +09:00
Haewon Kam
5cf28d9eef feat(front): 병원이 사이트에 실을 사진을 한 장씩 고르는 화면
싣기 어려운 사진(환자·전후 비교·시술 장면·효과 주장 그래픽)은 파일명이나
설명문으로 걸러지지 않아 사람이 눈으로 봐야 한다. 그 과정이 제품 밖에 있어
빌드마다 검토서를 따로 만들어 보냈다.

/images/:clinicId 를 추가한다. supporter_builds.report.images 를 읽어 미리보기
사이트의 사진을 한 장씩 보여 주고, 사용·제외를 supporter_inputs(key=image_review)
에 쌓는다. 워커가 apply_inputs 에서 읽어 다음 빌드에 반영한다.

- 첫 화면 대표 → 사이트에 실림 → 아직 안 쓰임 순으로 정렬한다. 중간에 그만둬도
  실제로 노출되는 사진은 다 보게 된다
- 판정 기준은 법 조항이 아니라 눈으로 확인할 수 있는 질문으로 적는다
- 중간 저장한다. 같은 key 는 최신 행이 유효값이라 다시 들어오면 이어서 본다
- 사진 파일은 preview_url 에서 불러온다. 배포 전 빌드는 안내 문구로 빠진다

대상이 병원 담당자라 제작 과정과 내부 규칙은 화면에 쓰지 않는다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-18 16:04:25 +09:00
Haewon Kam
796fa56106 feat(supporters): 주변 안내 축제를 회복 일정의 체류 기간 기준으로 먼저 보여 준다
주변 안내(/stay·/en/stay)의 축제 목록이 계절 묶음만 있고, 회복 일정 플래너에서 고른
기간과 이어지지 않았다(haewon 2026-09-16). 플래너가 체류 기간(도착일~출국 가능일)을
이 기기의 localStorage 에 남기고, 주변 안내는 그 기간과 겹치는 행사를 위로 올린다.

- Planner.astro: 결과를 그릴 때 체류 기간만 저장(시술·수술일은 저장하지 않음, 서버로도
  가지 않음). 행사 섹션에 "주변 안내에서 이 기간의 행사 전체 보기" 링크(/stay#plan:from..to)
- stay.astro·en/stay.astro: 카드에 data-start·data-end·data-km, "체류 기간" 칩(기본 숨김),
  기간 안내 문단. 스크립트가 URL 해시 또는 localStorage 에서 기간을 읽어 겹치는 카드를
  맨 앞으로 옮기고 칩을 켠다. 기간이 짧은 행사가 앞, 90일을 넘는 상시 행사는 뒤('상시 진행')
- 기간이 없거나 겹침이 없으면 화면은 이전과 같다. 저장소를 못 쓰는 브라우저는 조용히 건너뛴다
- global.css: 겹치는 카드 테두리·오른쪽 배지, 숨긴 칩
- run.mjs: planStrings.json 을 코드 갱신 목록에 넣는다. 기존 병원 폴더에 옛 문구 파일이
  남아 링크 글자가 비어 나온 것을 캡처로 발견했다

검증: 원진 빌드 게이트 통과, plan.test 14건 통과, 로컬 미리보기(Playwright)로
플래너 저장→주변 안내 정렬(20/24건, 짧은 행사 먼저)·해시 진입·기간 없음·영문·모바일 확인.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-16 09:47:01 +09:00
Haewon Kam
98a3e41837 chore(docs): 빌더 설명문 표현 정리
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-16 09:47:01 +09:00
Haewon Kam
d1c846b6aa fix(front): 루트 /favicon.ico 를 discovery 배포에 넣는다
미리보기 카드와 일부 브라우저는 <link rel="icon"> 을 보지 않고 /favicon.ico 를
그대로 요청한다. vercel.json 의 SPA 리라이트가 모든 경로를 index.html 로 보내서
아이콘 자리에 HTML(text/html 2,589B)이 도착했고, 공유 카드에 회색 링크 아이콘이 나왔다.

public/ 은 infinith-demo 배포와 공용이라 파일명을 discovery- 로 나눠 두었으므로,
빌드 때 이 배포의 아이콘만 루트 이름으로 복사한다. infinith-demo 빌드는 그대로다.

검증: discovery 빌드에 favicon.ico(16·32·48)·favicon-32·192·apple-touch-icon 생성,
기본 빌드에는 생기지 않음. tsc 0 에러.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 09:27:10 +09:00
Haewon Kam
216b2c4217 docs(business): 뷰성형외과 MOU·POC 범위 검토와 의료관광 허브 경쟁분석
2026-09-15 회의록 검토, Global Medical Hub PRD v0.2 검토, 경쟁 플랫폼·규제·시장 딥리서치,
병원 AEO/GEO 서비스 경쟁분석(4-10), 현재 조직으로의 사업 가능성, MOU·POC 권고 범위.

- 워드 보고서는 haewon 편집본이다. 초기 빌더(build_mou_hub_competition_docx.py)는
  결과 파일이 있으면 멈추고, 이후 변경은 patch_aeo_geo_competition_docx.py 처럼 제자리 삽입한다
- MD 초안 동봉

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-15 16:55:59 +09:00
Haewon Kam
bfde7f61b2 feat(landing): 히어로 문구를 'AI에게 병원을 묻습니다'로
특정 서비스명(ChatGPT와 Perplexity) 대신 AI로 바꿨다. webforai.kr 공유 이미지의
같은 문장도 함께 바꿨다. 아직 배포하지 않았다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-15 16:55:59 +09:00
Haewon Kam
5faf867b05 feat(measure): 네이버 AI 브리핑을 질문 뱅크 실측 엔진으로 추가
네이버 AI 브리핑은 공식 API가 없어 지금까지 답변 노출을 재지 못했다.
SerpApi naver_ai_overview(제3자 SERP API)로 조회하는 엔진을 붙였다(haewon 결정 2026-09-15).
search.naver.com robots.txt 가 수집을 금지하므로 고객 리포트·랜딩에는 넣지 않고
내부 측정까지만 반영했다. 고객 노출은 법무 확인 뒤에 따로 한다.

- run_question_bank_openai.py: --engine naver_briefing, --device, --no-cache(반복 측정 시 1시간 캐시 무시).
  브리핑이 없는 문항은 answer 를 비우고 briefing=false 로 기록. 인용 출처(sources) 보관
- monthly_ai_measure.py: 기본 엔진 3개(키 없으면 네이버 건너뜀),
  supporter_builds.report.naverBriefing(노출률·언급·출처 상위, legalReviewed=false)
- sentiment_qb.py: 빈 답변은 판정 API를 부르지 않고 미언급 처리
- merge_qb_results.py: NAVER 열·브리핑 없음 표시, 측정일을 생성일이 아닌 엔진별 실제 날짜로
- export_qb_excel.py: 네이버 열·NAVER상세 시트, --out 으로 기존 파일 보존.
  TOP50 점수는 기존 정의(ChatGPT+Perplexity, 만점 840) 유지

실측(뷰성형외과, 모바일, 2026-09-15): 브리핑 노출 119/120, 브랜드 질문 언급 36/36,
논브랜드 1/84. 인용 출처는 네이버 블로그 87·카페 51·굿닥 23·뷰 공식 20 순.
SerpApi 123회, 감성 판정 $0.37.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-15 16:55:59 +09:00
Haewon Kam
eaf60a9cf0 feat(supporters): 공유 카드(og) 영어화
해외 환자가 받는 링크라 카카오톡·슬랙 미리보기의 제목·설명을 영어로 낸다(haewon 2026-09-15).
site.json shareNameEn 이 있을 때만 적용하고, 없으면 기존 한국어 카드를 유지한다.

- 홈·한국어 페이지: "<영문명> Supporters · Answers to the questions patients ask first" + 영어 설명
- 영문 페이지: 자기 영어 제목·설명을 쓰되 섞인 한국어 상호를 영문 표기로 바꾼다
- 글(article): 본문이 한국어라 제목·설명은 그대로, 사이트 이름만 영어
- <title>·meta description 은 페이지 언어 그대로(검색 결과용)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-15 09:44:05 +09:00
Haewon Kam
7b22b86a27 chore: Word 잠금 파일 제외
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-15 09:36:53 +09:00
Haewon Kam
f6aae1c396 feat(share): 링크 공유 미리보기 이미지와 파비콘
카카오톡·슬랙·iMessage 에 사이트 주소를 붙이면 회색 링크 아이콘만 나왔다.
webforai.kr 은 og:image 와 파비콘이 없었고, 서포터즈 사이트는 robots.txt 가
미리보기 봇까지 막았으며 파비콘이 Astro 기본 로고였다.

- scripts/make_share_assets.py: 1200x630 공유 이미지(JPEG)와 파비콘 세트 생성.
  서포터즈는 해외 환자용 영어 판(병원 공식 영문 표기 shareNameEn), 파비콘은 영문 첫 글자.
- 워커 build 단계에서 자동 생성. briefs/<clinic>/clinic.json 에 shareNameEn 과 출처.
- Base.astro: og:image 기본값 /og.jpg, 크기·twitter:card·아이콘 링크.
- robots.txt(샘플): 미리보기 봇만 허용. 검색·AI 크롤러 차단과 noindex 는 유지.
- vite.config.ts: discovery 빌드에만 og:image·아이콘 태그. public/ 은 infinith-demo 와
  공용이라 파일명에 discovery- 접두어.
- Astro 기본 favicon.svg 삭제는 직전 커밋(f2fe99b)에 섞여 들어갔다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-15 09:36:45 +09:00
Haewon Kam
f2fe99b81d docs(reports): 답변엔진 확장 검토 보고서 (120문항 비용 추정·후보별 약관)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-15 09:36:33 +09:00
Haewon Kam
ac2ed79af1 chore(supporters): 오라클 네이버 소유 확인 값 (oracle.webforai.kr)
세 서포터즈 사이트의 네이버 소유 확인이 모두 배포됐다. 값 3개가 서로 다른 것과
사이트별로 맞게 나가는 것을 확인했다.

구글은 webforai.kr 도메인 속성을 DNS TXT 로 확인해 서브도메인 전부가 한 번에 덮인다.
네이버는 DNS 를 받지 않아 사이트마다 메타로 따로 확인한다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-14 16:09:32 +09:00
Haewon Kam
01ad08c5f3 chore(supporters): 원진 네이버 소유 확인 값 (wonjin.webforai.kr)
2026-09-14 서치어드바이저 발급분. 사이트마다 값이 달라 옮겨 쓰면 확인되지 않는다.
배포 확인: 뷰와 원진이 서로 다른 값을 내고 오라클은 아직 없다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-14 16:07:06 +09:00
Haewon Kam
9b717dbcfc fix(worker): 빌드마다 템플릿 화면 코드를 갱신 · 뷰 네이버 소유 확인 값
템플릿은 data 단계에서만 복사돼, --from build 로 돌리면 그 사이에 고친 화면 코드가
기존 병원 폴더에 닿지 않았다. 오늘 두 건이 이 이유로 배포되지 않았다.
- 검색엔진 소유 확인 메타: site.json 에 값이 들어갔는데 화면에 안 나왔다
- 회복 일정 플래너 계측(plan_procedure_select·plan_result·plan_save): 세 사이트 모두 없었다

refreshTemplateCode() 가 build 단계에서 화면 코드만 다시 덮는다.
src/layouts·components·pages·styles·lib 과 lib.ts·content.config.ts 가 대상이고,
병원 데이터·글·이미지는 건드리지 않는다. 병원별로 갈리는 astro.config.mjs·vercel.json 의
치환값, 구조 기준선, home_text 도 덮지 않는다. 내용이 같은 파일은 건너뛴다.

뷰 브리프에 네이버 서치어드바이저 소유 확인 값을 넣었다(viewpl.webforai.kr 용).
사이트마다 값이 달라 옮겨 쓰면 확인되지 않는다는 것을 주석에 적었다.

검증: 세 사이트 재배포 후 뷰에 소유 확인 메타가 나오고 다른 두 곳에는 안 나온다.
플래너 계측은 세 곳 모두 번들에 들어갔다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-14 16:01:44 +09:00
Haewon Kam
f68e4785bf chore(front): webforai.kr 네이버 소유 확인 메타 추가
네이버 서치어드바이저는 DNS TXT 를 받지 않아 메타 태그로만 소유가 확인된다.
구글은 webforai.kr 도메인 속성을 DNS TXT 로 확인해 메타가 필요 없다.

VITE_SITE=discovery 빌드에만 들어간다. infinith-demo 빌드에는 나가지 않는다.
서포터즈 사이트는 코드가 달라 briefs/<clinic>/clinic.json 의 site.naverSiteVerification
으로 각각 넣는다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-14 15:44:39 +09:00
Haewon Kam
247d29173d fix(supporters): 검색엔진 소유 확인 메타 태그를 실제로 렌더
site.json 에 googleSiteVerification·naverSiteVerification 키는 있었는데 어디에서도
메타 태그로 내지 않았다. 값을 넣어도 소유 확인이 되지 않는 상태였다.
색인 등록을 시작하려다 드러났다.

Base.astro 가 값이 있을 때만 메타를 낸다. 없으면 태그 자체가 나가지 않는다.
소유 확인은 색인 허용과 무관하므로 noindex 상태에서도 미리 해 둘 수 있다.

구글은 DNS TXT 로도 되지만 네이버 서치어드바이저는 메타 태그나 파일만 받는다.

검증: 값을 넣은 빌드에 메타 2개, 값 없는 빌드에 0개. 게이트 41/41, check 오류 0건, tsc 0.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-14 15:33:49 +09:00
Haewon Kam
3d8603f5e1 docs: 핸드오버 §5-4·§0 에 도메인 연결 완료 반영
webforai.kr 등록·네임서버 위임·주소 연결이 모두 끝났다. 연결된 주소 4개와
각 Vercel 프로젝트를 표로 적었다.

AI Discovery 랜딩을 별도 프로젝트(infinith-discovery)로 뺀 이유를 남겼다.
/discovery 가 infinith-demo 안의 경로라 도메인만 붙이면 기존 INFINITH 랜딩이 뜨고,
SPA 라 rewrite 로도 안 된다. 루트에서 무엇을 그릴지 VITE_SITE 로 정한다.

새 병원을 붙이는 순서를 2단계로 줄였다. 레지스트리에 서브도메인을 넣고
워커를 --domain 으로 돌리면 DNS·인증서는 Vercel 이 한다.

아직 꺼져 있는 자동연장·도메인 잠금을 상태 표와 프롬프트에 적었다.
이 도메인 하나에 병원 사이트가 전부 얹히므로 갱신을 놓치면 한꺼번에 멈춘다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-14 15:29:47 +09:00
Haewon Kam
7dd14f43bc feat(front): 배포별 head 메타 (제목·설명·og)
index.html 을 두 배포가 공용으로 쓰는데 제목이 하나뿐이라, webforai.kr 이 AI Discovery
랜딩을 띄우면서 제목은 'INFINITH - Infinite Marketing' 으로 나갔다. 설명과 og 태그는
아예 없었다. 답변엔진이 읽는 첫 신호라 비워 두면 안 되는 자리다.

VITE_SITE=discovery 빌드는 AI Discovery 제목·설명을, 아니면 기존 INFINITH 제목을 넣는다.
설명과 og:title·og:description·og:type·og:locale 도 함께 넣는다.

검증: 변수 없는 빌드는 기존 제목 유지, discovery 빌드는 새 제목. tsc 0 에러.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-14 15:28:04 +09:00
Haewon Kam
8bd168e1c7 fix(worker): 사이트 주소 설정을 build 단계에서도 적용
--from build 로 재배포하면 vercel.json 의 SITE_URL 이 갱신되지 않아 canonical 과
사이트맵이 옛 .vercel.app 주소로 나갔다. 그 코드가 data 단계에만 있었기 때문이다.
오늘 세 사이트를 새 도메인에 붙이고 확인하다 드러났다.

applySiteUrl() 로 떼어내 data 와 build 두 단계에서 부른다. 어느 지점부터 돌려도
주소가 반영된다. 색인 허용 여부(PUBLIC_INDEXABLE, X-Robots-Tag 제거)도 같은 함수가 맡는다.

검증: 세 사이트 재배포 후 canonical·사이트맵이 모두 새 주소로 바뀌고 noindex 는 유지된다.
- viewpl.webforai.kr · wonjin.webforai.kr · oracle.webforai.kr

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-14 15:27:08 +09:00
Haewon Kam
98d52f20f1 feat(front): 루트 페이지를 배포별로 분기 (VITE_SITE=discovery)
webforai.kr 이 AI Discovery 랜딩을 띄워야 하는데, /discovery 는 infinith-demo 안의
경로라 도메인만 붙이면 기존 INFINITH 랜딩이 뜬다. SPA 라 rewrite 로도 안 된다.
React Router 가 주소로 판단하기 때문이다.

VITE_SITE=discovery 로 빌드한 배포만 루트에 AI Discovery 랜딩을 놓는다.
값이 없으면 지금까지처럼 INFINITH 랜딩이다. infinith-demo 의 동작은 바뀌지 않는다.

한 코드베이스로 두 사이트를 내는 방식이라 랜딩을 고칠 때 양쪽에 한 번에 반영된다.

검증: 변수 없는 빌드와 VITE_SITE=discovery 빌드 모두 성공, tsc 0 에러.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-14 15:17:48 +09:00
Haewon Kam
682d426a2a docs: 파이프라인 설명에 주변 안내 복원 · 워커 단계 주석을 실제와 맞춤
haewon 지적: 파이프라인 설명에서 주변 안내(/stay·/en/stay)가 빠져 있었다.
오늘 머지가 떨어뜨렸던 바로 그 페이지인데 설명에 없으니 계속 잊힌다.
§0 프롬프트, §1 표 3행, §3 되는 것 세 곳을 고쳤다.

워커 주석의 단계 목록도 낡아 있었다. 실제 PHASES 는 18단계인데 주석은 15단계였고
planner·heroes·publish 가 빠져 있었다. 주석과 실제가 18개로 맞는 것을 확인했다.
단계마다 무엇을 만드는지 한 줄씩 붙였다. tourism 이 /stay 를 만들고 planner 가
/plan 을 만든다는 것이 이름만으로는 드러나지 않아 오늘 같은 혼동이 났다.

사용법 주석에 --domain 과 --indexable 을 추가했다. 둘 다 다음 세션이 쓸 인자인데
적혀 있지 않았다.

배포본 세 곳의 /stay·/en/stay 가 모두 200 인 것을 확인했다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-14 11:45:59 +09:00
Haewon Kam
d847027bf2 docs: 핸드오버에 도메인·레지스트리(§5-4) 반영 · §0 프롬프트 갱신
§0 프롬프트가 오늘 작업으로 낡았다. 브랜치 3개가 main 으로 합쳐졌고 작업 위치도
Productize-2(main)로 바뀌었다. 메인 체크아웃은 다른 작업의 미커밋 변경이 있어
건드리지 않는다는 것과, Supabase CLI 링크가 그쪽에만 있다는 것을 적었다.
결재 대기 중인 도메인 품의가 다음 세션의 첫 걸림돌이라 프롬프트 본문에 넣었다.

프롬프트에 원칙 두 줄 추가.
- 머지 직전에 브랜치 끝을 다시 읽는다. 오늘 이것을 놓쳐 첫 머지가 주변 안내와
  publish 단계를 떨어뜨렸다.
- 운영 DB 에 db push 를 쓰지 않는다. 이력이 실제와 어긋나 7개를 다시 실행하려 든다.

§5-4 신설: 주소 방식 결정과 근거, 지금 상태, 레지스트리에 넣은 컬럼·제약,
도메인 등록 후 순서(네임서버 위임 → --domain 재배포 → --indexable 은 붙이지 않음),
규모가 커질 때 대비할 것(병원 간 유사도 상한은 아직 없는 방어), DB 작업 주의.

§5 결정 대기 5번(도메인)을 결정으로 바꾸고, §7 파일 참조에 오늘 산출물을 넣었다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-14 11:37:08 +09:00
Haewon Kam
d4969da451 docs(db): 마이그레이션 파일과 운영 DB 대조표 (읽기 전용 조회)
migration repair 를 하기 전에 무엇이 다른지 먼저 확인했다. 이력에는 10개 중 3개만
기록돼 있고 나머지는 SQL 편집기로 직접 만든 상태라, 그대로 repair 하면 파일과 DB 가
다른데 같다고 기록돼 db diff·db pull 이 어긋난 결과를 낸다.

정본은 마이그레이션 파일로 정했다(haewon 2026-09-14).

결과: 표 17개 중 15개가 정확히 일치한다. 손볼 곳은 세 가지다.
- clinic_registry: 파일에만 is_active·verified_at·verified_by, DB 에만 notes.
  notes 는 74곳 전부에 값이 있어 실제로 쓰이므로 파일에 반영해야 한다.
- channel_latest·channel_weekly_delta 는 뷰이고 정의가 마이그레이션 파일에 없다.

DB 는 조회만 했고 바꾸지 않았다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-14 11:26:18 +09:00
Haewon Kam
9d8360d634 feat(db): clinic_registry 에 서브도메인 컬럼·유니크·예약어 제약 추가
서포터즈 사이트를 공용 도메인(webforai.kr)의 병원별 서브도메인에 두기로 했다.
수백 곳으로 늘 때 이름을 코드가 지으면 "뷰"·"연세"처럼 여러 병원이 쓰는 이름에서
충돌이 나므로, 이 표를 서브도메인의 단일 원본으로 둔다.

컬럼
- subdomain: 공용 도메인 앞에 붙는 라벨 (viewpl → viewpl.webforai.kr)
- clinic_slug: 워커 --clinic 값. 빌드 폴더·Vercel 프로젝트 이름에 쓰여 서브도메인과 다를 수 있다
- subdomain_assigned_at: 배정 시각. 색인 전환 전에 주소를 바꾸는 편이 싸므로 판단에 쓴다

제약
- subdomain·clinic_slug 각각 대소문자 무시 유니크
- 형식: 소문자·숫자·붙임표, 2~40자, 붙임표로 시작·끝 금지, 연속 붙임표 금지
- 예약어 70여개 차단. 메일·인증(mx·dmarc·autodiscover)을 병원에 내주면 메일 수신과
  도메인 소유 확인이 깨지고, 회수하는 시점에는 이미 색인된 주소가 깨진다

배정: 뷰 viewpl · 원진 wonjin · 오라클 oracle.
오라클은 이 레지스트리가 성형외과 73곳으로 만들어져 등록돼 있지 않아 함께 넣었다.

운영 DB 적용 중 확인한 것: 마이그레이션 이력에 10개 중 3개만 기록돼 있고 나머지는
SQL 편집기로 직접 만든 상태다. 그래서 db push 대신 이 파일만 따로 적용했다.
20260406_clinic_registry.sql 에 있는 verified_by·verified_at·is_active 는 실제 표에 없다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-14 11:09:52 +09:00