webforai.kr 빌드 결과에서 실제 병원명·도메인, Google·Supabase 키, reports/·assets/clients/ 파일을 찾으면
빌드를 멈춰 Vercel 배포가 진행되지 않게 한다. 목록은 plugins/public-bundle-denylist.json.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- 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>
lib/supabase.ts 의 최상단 createClient() 가 부수효과로 남아, 쓰는 곳이 없는 공개 배포에도
Supabase URL·anon 키가 실렸다. 이 키로 marketing_reports 등 실제 병원 데이터 테이블을 읽을 수 있었다.
supabase 모듈을 부수효과 없는 모듈로 표시해 쓰지 않으면 번들에서 빠지게 한다. infinith-demo 빌드는 그대로 쓴다.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
public/ 은 infinith-demo 와 공용이라 reports/viewclinic(영문 진단 리포트 HTML·PDF)과
assets/clients/(병원 채널 스크린샷)가 공개 배포에도 올라가 주소만 알면 열렸다.
discovery 빌드의 writeBundle 에서 두 폴더를 지운다. infinith-demo 빌드는 그대로다.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- 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>
- 랜딩 샘플 리포트·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>
브라우저로 내려간 값은 무엇이든 공개다. 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>
haewon 2026-10-01 지시로 두 사이트에서 팩스 0504-335-6463 을 뺀다.
aio2o.webforai.kr 쪽도 같은 날 함께 제거했다.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
한 번 문의를 보낸 뒤 닫았다가 다시 열면 폼 대신 "문의가 접수되었습니다"
화면이 그대로 떠서 두 번째 문의를 할 수 없었다. 모달을 닫아도 컴포넌트가
언마운트되지 않아 state 가 'done' 으로 남아 있던 탓이다.
열릴 때마다 입력값과 상태를 비운다.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
푸터 (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>
haewon 지정. 1행(로고 · 저작권/태그라인 · 메뉴) → 구분선 → 연락처 한 줄.
- 문의하기를 연락처 줄 오른쪽 버튼에서 1행 메뉴로 옮긴다. aio2o 푸터가
회사 소개·솔루션과 같은 줄에 문의하기를 두는 것과 맞춘다.
- 연락처는 "전화 … · 이메일 …" 한 줄로 왼쪽 정렬하고 값에만 밑줄을 준다.
- 사업자명·주소는 넣지 않는다. 확인된 값이 없어 지어내지 않는다.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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>
연락처 통일
- 사이트 안에 이메일이 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>
기존 상단 띠 배너("샘플 사이트입니다")는 스크롤하면 사라져 병원이
둘러보는 동안에는 초안인지 최종본인지 판단이 서지 않았다.
- sticky 헤더 안에 "초안" 배지 추가(스크롤해도 계속 보임), 영문판도 동일
- 문구를 "샘플 사이트입니다"에서 상태 중심으로 변경:
"초안입니다. 검색에 노출되지 않으며, 병원 확인 전입니다."
- 띠 배너 글자 크기·굵기 상향(0.83rem/500 → 0.95rem/600)
- 인쇄 시 배지 숨김(기존 배너 규칙과 동일하게)
- INDEXABLE=true(공개본)에서는 배너·배지 모두 그대로 뜨지 않음
templates/supporters-astro(정본)와 supporters/(현재 작업 사본) 양쪽에
동일하게 반영. 이미 배포된 서포터즈 사이트(예: supporters-jkplastic)에는
재빌드·재배포 전까지 적용되지 않는다.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
증거(진단)와 처방(할 일)을 한 페이지에 두면 스크롤이 너무 길어져
둘 다 읽기 힘들다는 지적에 따라 단계를 나눴다.
- /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>
- 영문 사이트(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>
버튼만 두면 무엇에 동의하는지 모른 채 누르게 된다. 진단을 요약하고, 이 빌드가 무엇을
보완하는지 말한 뒤 누르게 한다.
처음 만든 판은 실행 항목을 카테고리로 갈라 "이 빌드로 채워지는 항목 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>
"한 장씩 봐 주세요" 같은 요청문이 읽고 넘어가는 설명문과 같은 무게로 놓여 있어
지나치기 쉬웠다. 역할별 토큰과 클래스를 두고 화면에 적용한다.
.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>
예전 전략마케팅 툴은 분석 진행 화면에서 단계를 보여 주고 리포트로 넘어갔다.
지금 버전은 진단과 사이트 빌드가 서로 모르는 상태로 떨어져 있어, 진단을 읽은 병원이
다음에 무엇을 하는지 알 수 없었다. 진단 → 빌드 → 사진 확인 → 확인 항목을 한 줄로 잇는다.
- /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>
랜딩 신청 모달은 남긴 연락처로 안내한다고 말하는데 보내는 코드가 없다.
운영자도 신청이 들어온 사실을 알 방법이 없다. 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>
싣기 어려운 사진(환자·전후 비교·시술 장면·효과 주장 그래픽)은 파일명이나
설명문으로 걸러지지 않아 사람이 눈으로 봐야 한다. 그 과정이 제품 밖에 있어
빌드마다 검토서를 따로 만들어 보냈다.
/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>
주변 안내(/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>
미리보기 카드와 일부 브라우저는 <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-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>
특정 서비스명(ChatGPT와 Perplexity) 대신 AI로 바꿨다. webforai.kr 공유 이미지의
같은 문장도 함께 바꿨다. 아직 배포하지 않았다.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
네이버 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>
해외 환자가 받는 링크라 카카오톡·슬랙 미리보기의 제목·설명을 영어로 낸다(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>
카카오톡·슬랙·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>
세 서포터즈 사이트의 네이버 소유 확인이 모두 배포됐다. 값 3개가 서로 다른 것과
사이트별로 맞게 나가는 것을 확인했다.
구글은 webforai.kr 도메인 속성을 DNS TXT 로 확인해 서브도메인 전부가 한 번에 덮인다.
네이버는 DNS 를 받지 않아 사이트마다 메타로 따로 확인한다.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-14 서치어드바이저 발급분. 사이트마다 값이 달라 옮겨 쓰면 확인되지 않는다.
배포 확인: 뷰와 원진이 서로 다른 값을 내고 오라클은 아직 없다.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
템플릿은 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>
네이버 서치어드바이저는 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>
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>
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>
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>
--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>
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>
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>
§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>
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>