"구글 로그인 버튼이 없다" 는 지적이 맞았다. 버튼을 LoginPage·SignupPage 에만 붙여 뒀는데,
이 앱에서 사장님이 실제로 로그인 화면을 만나는 자리는 **에디터 진입 관문**(EditorSignInGate →
SignInForm)이다. 정작 거기엔 없었다.
- features/auth/SignInForm: 구글 버튼 추가. 관문·로그인 화면이 같은 폼을 쓰므로 한 곳만 고치면 된다
- lib/googleIdentity: 스크립트를 ?hl=ko 로 받는다. renderButton 의 locale 옵션은 안 먹었다 —
'ko'·'ko_KR' 둘 다 'Continue with Google' 이 그대로 나왔다(실측)
- 버튼 문구는 signin_with('Google 계정으로 로그인'). 가입 화면만 signup_with 로 둔다.
문구 자체는 고를 수 없다 — 구글 브랜드 가이드라 GIS 가 주는 번역을 그대로 쓴다
브라우저 확인(localhost:80): 로그인 화면에 'Google 계정으로 로그인' 한글 노출.
tsc·eslint·vite build 통과.
main 이 5ef3e5a 로 문 앞 게이트(d6a6c8e·b94daa9)를 되돌렸다. 근거가 내가 못 본 것이었다 —
`/` 는 자기 화면 없이 /builder 로 넘기기만 하므로 **문 앞 가드는 곧 루트 가드**이고,
앱을 열자마자 로그인 화면이 된다. 그 결정을 따르고, 되돌리기에 휩쓸린 것만 복구한다.
- app/router: `/signup` 라우트 복구. 라우터를 통째로 되돌리면서 같이 날아갔고,
그 결과 로그인 화면의 [회원가입] 링크가 404 였다
- features/auth/SignInForm: 토큰 심는 순서(signIn → me)를 lib/session 으로. 이 파일 맨 위
주석이 경고하던 그 중복이다 — 관문이 되살아나면서 사본도 같이 돌아왔다
- pages/BuilderPage: 에디터 관문(main)과 상단 바 사용자 표시(이쪽)를 함께 둔다.
충돌은 `authUser`/`user` 이름뿐이었다
- docs/DEVLOG: 인증 항목이 되돌리기에 휩쓸려 사라졌다. 지금 설계(에디터 진입 관문)에 맞춰
다시 썼다 — 문 앞 가드를 시도했다 되돌린 이력도 함께 남긴다
- docs/ARCHITECTURE: 인증 모델 서술과 '아직 안 한 것' 을 지금 상태로
브라우저 확인: 위저드는 로그인 없이 열림 → 가입 → 사이드바 '김사장 · 달빛스테이' →
에디터 상단 바 동일 표시 → 로그아웃. 구글 버튼 렌더까지 확인(실제 로그인은 client_id 필요).
pytest 534 passed / 4 failed(전부 기존 실패). tsc·eslint·vite build 통과.
## 업종 교체 (tour → clinic)
PlaceCategory 코드 4번의 의미를 바꾼다. 아직 배포 전이라 데이터 마이그레이션은 없다.
- category_schema: tour_activity.json → clinic.json. 체험 스키마(안전 유의사항·우천 시
운영·준비물)를 진료 스키마(진료과목·의료진·상담료·보험 적용·야간/주말진료)로 바꿨다.
unit 은 프로그램 → 시술이다(마취 방식·회복 기간·권장 횟수·시술 후 주의사항).
- 소개문 계열만 allow_llm 이다. 시술 효과·비용 같은 값은 LLM 이 못 쓴다 —
이 레포의 "검증 전에는 발행 금지" 규칙이 의료 문구에서 특히 중요하다.
- jsonld: TouristAttraction → MedicalClinic. 프론트 AeoReadiness 의 같은 표도 맞췄다.
- 색 팔레트를 병원 톤(클린 블루·세이지·누드·모노)으로, 아이콘을 Compass → Stethoscope 로.
- mock_adapter 목데이터를 시술 기준으로 교체. 스키마에 없는 key 를 쓰면 수집이 죽는다.
- site_payload 의 기본 섹션표를 에디터(industryData)와 같게 맞췄다 —
test_site_theme 이 이 둘을 대조한다.
## 로그인 관문 되돌리기 (b94daa9·d6a6c8e revert)
두 커밋이 /builder 를 통째로 RequireAuth 뒤로 옮겨 `/` 가 곧바로 로그인 화면이 됐다.
`/` 는 자기 화면 없이 /builder 로 넘기기만 하므로, 문 앞 가드는 곧 루트 가드다.
위저드를 열어 두고 에디터 진입에서 한 번 받는 969fb67 설계로 되돌린다.
d6a6c8e 가 스스로 "969fb67 과 정면으로 다른 설계"라고 적어 두었다.
## 그 밖
- test_site_theme 의 경로가 solution/front 로 남아 있었다(frontend 개명 누락).
- .dockerignore: 이 머신에 buildx 가 없어 레거시 빌더가 돌고, 그러면
nginx/Dockerfile.dockerignore 가 무시된다. 루트 것 하나로 두 이미지를 다 커버한다.
검증: frontend·admin·site lint·build 0. 백엔드 534 passed / 4 failed —
그 4개(test_build_publish 3 · test_snapshot 1)는 이 변경 전부터 실패하던 것이다.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xa8ME5FQJy4VA8pPokTo1a
같은 날 두 자리에서 같은 문제를 풀어 관문이 두 겹이 됐다. 둘 다 두면 문 앞(RequireAuth)이
먼저 걸려 에디터 관문은 영영 안 뜨는 죽은 코드다. 문 앞을 남긴 이유는 열어 둔 값이
공짜가 아니었기 때문이다.
에디터 관문을 쓰려면 2단계가 토큰 없이 지나가야 했고, 그래서 토큰이 없을 때 서버를 부르지 않고
입력값으로 신원을 세우는 우회로가 생겼다(confirmManual). 그건 이 레포의 단 하나의 규칙
— 검증 전에는 수집·발행 금지 — 을 화면이 비켜 가는 모양이고, 대가는 "로그인 뒤에 검증을 다시"다.
게다가 가입이 이제 그 자리에서 끝나므로(가입 응답에 토큰이 실린다) 문 앞 로그인의 마찰은
"만들어 보기도 전에 막는다" 던 시절보다 훨씬 작다.
- features/auth/EditorSignInGate·SignInForm 삭제. 관문이 하나면 폼도 하나다 —
SignInForm 이 경고하던 'signIn → me 를 두 벌로 들고 있다' 를 lib/session 한 곳으로 모았다
- Step2PlaceSearch: 토큰 없을 때 검증을 건너뛰던 두 갈래 제거
- usePlaceSearch: confirmManual 제거. 토큰이 없으면 이제 진짜 '만료'다(문 앞을 통과했으므로)
— 문구를 사실대로 되돌린다
- BuilderPage: 에디터 진입 분기 제거
되돌리려면 app/router 의 RequireAuth 를 벗기고 969fb67·22b7623 을 되살리면 된다.
tsc·eslint·vite build 통과(사장님 앱·admin).
위저드를 걷는 동안 '로그인이 만료되었습니다' 가 떴다. 만료가 아니라 **한 번도 로그인한 적이
없는 것**이었다(자동 로그인 계정이 없으면 ensureAutoSession 이 즉시 끝난다). 문구가 사실과 달라
고장으로 읽혔다.
- features/auth/EditorSignInGate: 에디터에 들어갈 때만 로그인을 받는다. 위저드 1~5단계는
요구하지 않는다 — 만들어 보기도 전에 막으면 아무도 안 만든다. /login 으로 튕기지 않는 이유는
위저드에서 쌓은 상태를 들고 돌아올 방법을 사장님이 알 수 없기 때문이다
- features/auth/SignInForm: 로그인 화면과 관문이 같은 폼을 쓴다. 두 벌로 두면 토큰을 심는
순서(signIn → me)가 한쪽에서만 지켜지고, 그 실수는 "로그인은 됐는데 계속 401" 로 나타난다
- usePlaceSearch: 토큰이 없을 때의 문구에서 '만료' 를 걷어낸다. 검색은 서버가 토큰을 요구하므로
(place.py 전 엔드포인트가 IsValidAccessToken) 프론트가 없앨 수 있는 제약이 아니다
tsc·eslint·vite build 통과