Commit Graph

305 Commits

Author SHA1 Message Date
db7c9254f5 [feat] solution/backend: 목업(stay·stay2) 사업장 글이 게재되면 index.html 의 미니블로그만 갈아 끼움
stay 는 굽기 보호 주소라 그 사업장 글을 승인하면 발행이 거절되고 글은 승인 상태로 남았다.
목업 화면의 미니블로그는 파일에 박힌 글이라 승인해도 바뀌지 않았다.

- services/mockup_blog.py: window.__SITE_PAYLOAD__.posts 만 교체 — 기존 글 유지 · 새 글 추가 · 최신순, 직전 파일은 index.html.bak-blog
- build_service: 발행 주소가 stay·stay2 면 굽지 않고 글만 갱신 후 승인분을 게재로(버전·Teams 실패 알림 없음)

테스트 3건 추가(test_mockup_blog). 빌드·블로그·롤백·썸네일 105 passed

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 11:26:54 +09:00
3ecd7b3e18 merge: feat/owner-blog-menu(최고관리자 미니블로그 관리 메뉴) 을 main 에 병합
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 11:15:05 +09:00
654fb51456 [feat] solution/frontend: 최고관리자 이상은 내 사이트 더보기에 미니블로그 관리 — 일반 사장님에게는 숨김
어제 더보기에서 미니블로그 관리를 통째로 뺐더니 최고관리자도 들어갈 길이 사이트관리 화면뿐이었다.

- SitesPage: role >= OWNER 일 때만 더보기에 미니블로그 관리(/blog?placeId=)

frontend tsc·eslint 통과

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 11:15:04 +09:00
ade3bc0f8e [feat] solution/backend,deploy: 미니블로그 자동 스윕 수신자를 좁힌다 — BLOG_AUTO_OWNER_IDS
발행된 사이트가 전부 자동 생성·발송 대상이라, 테스트로 만든 업장까지 매일 알림을 받는다.
실측(2026-10-01): 발행 25곳 중 스테이,머뭄 5개·게으르게펜션 2개 등 중복·테스트 업장이
상당수였고, 수신자는 내부 계정 7명이었다. 시험 운영 중에는 받을 사람을 좁혀 둔다.

- blog_jobs._for_auto_sweep: user_id 목록으로 거른다. **비면 전체**다 — 운영 기본값이라
  칸이 비었다고 기능이 꺼지면 안 된다(쉼표만 있는 입력도 전체로 친다)
- generate_drafts · send_reviewed 두 스윕에 **같이** 건다 — 한쪽만 걸면 만들어 놓고
  안 보내거나 그 반대가 된다

★★ 거르는 자리가 `_published_places()` 안이 아니다. 그 함수는 빌더의 [지금 생성하기]
   (`generate_range`·`generate_one_for_date`)도 쓰는데, 거기서 거르면 **목록 밖 사장님이
   자기 화면에서 자기 글도 못 만든다** — 자동으로 보낼지와 직접 만들지는 다른 질문이다.
   테스트가 그 두 함수의 소스에 `_for_auto_sweep` 이 없는지까지 본다.

★ 목록 밖 업장의 글은 건너뛸 뿐 **버리지 않는다** — REVIEWED 로 남아 목록을 풀면 그때
  나간다. 거기서 SENT 로 찍으면 아무에게도 안 간 글이 보낸 것으로 기록된다.

테스트 5건 추가. blog 관련 78 passed

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-10-01 11:12:44 +09:00
66908cb5c6 Merge remote-tracking branch 'origin/main' 2026-10-01 10:52:40 +09:00
519ed4a79b [fix] solution/backend,deploy: 미니블로그 재고 자동 채우기를 스위치로 격리 — 기본 꺼짐
2026-10-01 대표 지시("이거는 안보내도 될것같아"). 켜면 발행된 사이트 **전체**가 대상이고
업장당 최대 30건이라 첫 회차에 AI 호출이 한꺼번에 일어난다 — 그 비용은 사람이 정할 일이지
기본값이 정할 일이 아니다. 글은 빌더의 [지금 생성하기] 로 사장님이 직접 만든다.

★ add_job 을 다시 지우지 않고 스위치로 뺀 이유: 바로 앞 커밋(4650125)이 고친 게
  "등록 자체가 없는데 주석만 새벽에 채운다고 말하는" 상태였다. 지우면 그 상태로 돌아가
  **끈 것과 빠뜨린 것이 구별되지 않는다** — 증상("메일이 안 온다")을 보고 발송 쪽을
  뒤지게 된다. 지금은 꺼져 있다는 사실이 코드에 남는다.

★ 발송(09:00)은 이 스위치와 무관하게 항상 돈다 — 손으로 만든 글도 나가야 한다.

- scheduler/__init__: BLOG_AUTO_DRAFTS_ENABLED=1 일 때만 blog-drafts 등록
- docker-compose · .env.example: 스위치 전달, 기본 0
- docs/MINI_BLOG 2절: 기본 꺼짐과 그 이유, 예전에 등록이 없었다는 실측

테스트 5건(2건 신설 — 기본 꺼짐 · 켜면 등록됨), 전부 통과

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-10-01 10:52:31 +09:00
c818f3b30d Merge branch 'main' of https://gitea.o2o.kr/Web4ai/o2o-site-AEO 2026-10-01 10:19:31 +09:00
4650125c6c [fix] solution/backend: 미니블로그 자동 게재가 양쪽 끝에서 끊겨 있었다 — 생성 미등록·승인 후 발행 사망
증상은 둘 다 "메일이 안 온다" / "승인했는데 홈페이지가 그대로" 로만 보인다. 서버는 정상으로
뜨고 로그에도 에러가 없어서 눈으로 원인을 못 찾는 종류다.

① 재고 채우기가 스케줄러에 **등록돼 있지 않았다.** `scheduler/jobs.py` 에 함수는 있고
   `__init__.py` 주석도 "새벽에 재고를 채운다" 라고 말하는데 add_job 한 줄이 없어 한 번도
   돈 적이 없다. 09:00 발송만 돌고 보낼 글은 0건이었다 — 지금 DB 의 254건은 전부 화면의
   [지금 생성하기] 로 손으로 만든 것이다.
   → 04:10 KST 등록(발송보다 앞서야 그날 아침에 나갈 재고가 있다)

② 승인 뒤 재발행이 죽는다. `post_service._enqueue_build` 가 requested_by 에
   "blog-approval" 이라는 **라벨**을 넣었고 `build_service._log` 가 그걸 uuid.UUID() 에
   넣다 ValueError 를 던졌다. 하필 _log 는 사이트를 다 구운 **뒤**, sites.status 를
   PUBLISHED 로 찍기 **전**에 불린다 — 굽기는 끝났는데 발행만 안 된 채 3회 재시도 후 DEAD.
   실측: BUILD 잡 5건(2026-09-23~09-30)이 전부 이 원인이고 전부 미니블로그 승인분이었다.
   → 호출부는 사장님 ID 를 넣고, 파서(_actor_uuid)는 모양이 틀리면 기록만 비우고 진행한다.
     감사 기록 한 줄이 발행을 막는 것은 순서가 뒤집힌 것이다. rollback_service 도 같은 파서.

- scheduler/__init__: blog-drafts 등록 + 왜 빠져 있었는지
- services/build_service: _actor_uuid 신설, _log 가 그것만 쓴다
- services/post_service: requested_by = str(owner_user_id)
- services/rollback_service: 같은 파서 재사용

테스트 7건 추가(build 4 · scheduler 3), 전부 통과.
test_build_publish.py 의 기존 실패 13건은 변동 없음 — 원본으로 되돌려 측정해 확인했다
(원본 13 failed/2 passed, 변경 후 13 failed/6 passed). 그 13건은 컨테이너 테스트 DB
쪽 문제다(로그: lease 갱신 실패 InvalidCatalogNameError) — 이 변경과 무관하고 미해결로 남긴다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-30 17:31:10 +09:00
34d9d12c3f [fix] solution/frontend: 카카오 버튼이 정작 /login 에 안 떴다 — 로그인 폼이 세 벌이다
SignInForm 에만 붙였는데 `/login`·`/signup` 은 그 컴포넌트를 쓰지 않고 **각자 폼을 갖고
있다**(구글 버튼도 세 군데에 따로 적혀 있었다). 번들에는 키가 구워졌고 isKakaoLoginEnabled()
도 true 였는데 화면에만 없어서, 설정 문제로 보이는 종류였다.

- pages/LoginPage: 카카오 버튼 추가. returnTo 로 `from`(원래 가려던 곳)을 넘긴다
- pages/SignupPage: 가입도 같은 동작이라 같은 버튼을 쓴다(처음 온 회원번호면 백엔드가 생성)

lint(react-router typegen + tsc + eslint) 통과

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-30 16:58:13 +09:00
7ee6911836 Merge remote-tracking branch 'origin/main' 2026-09-30 16:52:51 +09:00
3253166622 [feat] solution,deploy: 카카오 로그인을 인가 코드 교환으로 — 시크릿을 서버에 가둔다
콘솔에 발급된 설정이 설계와 어긋나 있었다. 클라이언트 시크릿이 켜져 있고 Redirect URI 가
프론트 경로(/auth/kakao/callback)인데, 코드는 "프론트가 액세스 토큰을 들고 온다" 는
전제였다 — 시크릿을 번들에 구울 수 없으므로 그 흐름으로는 토큰을 받을 수 없다.

- services/external/kakao_identity: access_token 검증 → **인가 코드 교환**으로 교체.
  ★ 우리 REST 키·시크릿으로 교환한 토큰이라 정의상 우리 앱 것이다 — 구글의 aud 대조에
    해당하는 검사(KAKAO_LOGIN_APP_ID)가 구조적으로 필요 없어져 설정 칸째로 지웠다.
  ★ 로그인 앱과 챗봇 앱이 다르면 로그인은 되는데 채널 매칭만 조용히 안 된다 —
    REST 키가 어긋나면 경고 로그를 남긴다.
  ★ 미인증 이메일은 신원으로 쓰지 않는다(is_email_valid·is_email_verified 둘 다). 이 값으로
    auth_service 가 다른 수단 가입과의 충돌을 판정한다.
- config/config_models: KAKAO_LOGIN_APP_ID → REST_API_KEY · CLIENT_SECRET · REDIRECT_URI
- router/v1/auth: Req_KakaoLogin {access_token} → {code, redirect_uri}
- frontend: lib/kakaoIdentity(인가 URL · sessionStorage state) · KakaoSignInButton ·
  pages/KakaoCallbackPage(코드는 1회용이라 ref 로 한 번만 보낸다) · routes
  ★ 로그인 폼에서 카카오를 구글 위에 둔다 — 이 수단으로 들어와야 6자리 코드 없이 채널이 이어진다
- compose · nginx/Dockerfile: REST 키·Redirect URI 만 프론트로 흘려보낸다. 시크릿은 안 나간다
- 어드민 키는 저장하지 않는다 — 탈퇴까지 되는 키인데 로그인에는 쓰이지 않는다
- api/generated: orval 재생성. 카카오 외에도 오래 밀려 있던 문구·agent 경로가 함께 따라왔다

테스트 8건 추가(tests/test_kakao_identity.py), 관련 85 passed
(test_auth::test_google_login_is_off_when_client_id_is_empty 는 기존 실패)
lint(react-router typegen + tsc + eslint) 통과

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-30 16:51:05 +09:00
4971f82d7b merge: fix/unpublish-removes-page(Teams 는 장애만) 을 main 에 병합
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 16:37:24 +09:00
b75c56a0d6 [fix] solution/backend: Teams 는 장애만 — 수집·발행 시작·완료 알림 제거, 빌드 실패 중복 발송 정리
수집 1회에 3~4장, 발행 1회에 2장씩 와서 장애 알림이 묻혔다. 빌드·되돌리기 실패와 수집 예외는
기존 build_failed · job_dead 알림이 이미 보내고 있어 같은 사고가 두 번씩 갔다.

- collect_service: 시작·완료 요약·예외 알림 제거 — 채널 크롤링 실패만 남긴다(채널별 결과는 잡 결과 channels 에 그대로)
- vision_service: 실패가 있을 때만
- build_service · rollback_service: activity 알림 되돌림 — build_failed · recovery 가 그대로 담당
- docs/ALERTS.md: activity 행을 실제 발송 범위로

관련 테스트 17개 파일 297 passed · 2 failed(test_search_console_service — 변경 전에도 실패)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 16:37:24 +09:00
ca0bea77a7 [feat] solution/backend: 카카오 로그인 — 회원번호로 카톡 채널을 코드 없이 잇는다
★★ 챗봇 웹훅의 user.properties.appUserId 는 카카오 로그인의 회원번호와 **같은 값**이다
(카카오 공식 문서, 봇에 앱키가 물려 있을 때). 그래서 카카오로 로그인만 해 두면 채널에
말을 거는 순간 누구인지 알 수 있고, 6자리 코드 절차가 필요 없어진다.

- external/kakao_identity: 액세스 토큰을 카카오에 되물어 확인한다. ★ 응답의 app_id 를
  우리 앱과 대조하는 것이 구글의 aud 검사에 해당한다 — 이게 없으면 남의 앱 토큰으로
  우리 계정이 된다. 이름·이메일은 동의 항목이라 못 받아도 로그인은 되게 했다
- auth_service.kakao_login: google_login 과 같은 세 갈래. 이메일이 겹쳐도 자동으로
  잇지 않는다(DECISIONS 1 — 계정 선점)
- kakao_link_service.link_by_app_user_id: 자동 매칭. ★ 이미 다른 사장님에게 묶인
  카톡은 빼앗지 않는다 — 조용히 빼앗으면 앞사람이 남의 가게를 보게 된다
- ★ 코드 경로는 그대로 둔다: id/pw·구글 가입자에겐 appUserId 가 없고, 봇에 앱키가
  안 물린 환경에서는 값 자체가 안 온다

★ 함께 고친 것 — services/agent/tools.py 가 사라진 site_payload._DEFAULT_THEME 를
보고 있었다(c690862 템플릿 정의 통합에서 이름이 없어졌는데 이 한 줄만 남았다).
**대화의 섹션 기능이 통째로 죽어 있었고** 웹훅이 AttributeError 를 삼켜 "지금은
처리할 수 없어요" 로만 보였다 — common/template_catalog.industry_of 로 바꿨다.

test_kakao_link·test_kakao_webhook·test_agent_runtime·test_auth 194 passed
(신규 4: 자동 매칭·미가입자·빼앗지 않음·재진입). 남은 1건은 컨테이너에 실제
GOOGLE_CLIENT_ID 가 있어 나는 기존 실패다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-30 16:27:54 +09:00
283970b2ba [feat] ontology: 숙소 검색 API — 검색어로 맞는 숙소와 SEO 메타를 돌려준다
다른 세션(SNS AEO SEO 팔로워 수집)에서 만들던 작업이 작업 트리에 미커밋으로
남아 있었다. 잃지 않게 그 자리에서 커밋한다 — 커밋 하나에 무관한 변경을 섞지
않으려고 카카오 로그인 작업과 분리했다.

- POST serving/lodgings/search — 지역·유형·표기 변형을 받아 숙소와 SEO 메타를 돌려준다
- ★ SEARCH_FLOOR=0.94: 짧은 한글 키워드는 전혀 다른 말도 코사인이 높다
  (dedup.service 실측: '선유도 펜션' ↔ '새만금 펜션' = 0.936). 그래서 벡터 갈래는
  이 값 이상만 "같은 뜻" 으로 치고, 지역 구분은 벡터가 아니라 위치 갈래에 맡긴다

검증: npm install 후 tsc --noEmit — src/ 오류 0(기존 scripts/ 오류 2건은 이번 변경과 무관).
런타임 동작은 확인하지 못했다(DB·임베딩 제공자가 필요하다).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-30 16:00:22 +09:00
092a350182 [chore] solution/backend: 카톡 웹훅에 발화자 properties 로그 — 카카오 로그인 매칭 가능성 확인
user.properties.appUserId 는 봇 설정에 앱키가 물려 있을 때만 오고, 그 값이
**카카오 로그인으로 받는 값과 같다**(카카오 공식 문서). 오면 6자리 코드 절차 없이
사장님을 바로 알아볼 수 있다.

★ 로그인 작업을 다 만들고 나서 "키가 안 온다" 를 알면 그게 제일 비싸다 —
그래서 배선보다 이 한 줄이 먼저다.

★ 값 자체는 남기지 않는다. 사용자 식별자라, 어떤 키가 왔는지와 길이만 남긴다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-30 15:57:04 +09:00
f1d0f765d1 Merge branch 'feat/social-caption-keywords-v2' 2026-09-30 15:43:54 +09:00
e9849584c4 [feat] solution/backend: SNS 캡션 첫 문장에 지역·업종·상호
겨냥하는 것이 AI 검색 노출인데 캡션에 그 단서가 하나도 없었다. "군산 숙소" 같은
지역+업종은 실제 검색어이고 AI 가 인용할 때 잡는 말이다.

★ 해시태그가 아니라 문장에 녹인다. AI 는 태그가 아니라 문장을 읽고, 이 글은
사장님 개인 계정으로 나가는 발화라(DECISIONS 8) 태그 나열은 그 전제와 어긋난다.
기존 해시태그 금지는 그대로 둔다.

★ 지역·업종은 fact 가 아니라 카카오 로컬 검증을 통과한 place 값이다. ground_check
근거에 같이 얹는다 — 안 얹으면 본문에 쓴 순간 '근거 없는 주장' 으로 반려되고
3회 재시도를 태우고 실패한다. 상호명을 그렇게 다루던 방식 그대로다.

- prompts/social: caption_category("숙박업소"→숙소) · caption_region(
  "전북특별자치도 군산시"→"전북 군산"). ★ copy.py 라벨과 region_label() 을 그대로
  쓰지 않는다 — 저쪽은 홈페이지 본문·쇼케이스 목록용이라 그 자리에선 그게 맞다
- 표에 없는 시·도는 그대로 둔다 — 줄이려다 엉뚱한 지역이 되는 것보다 낫다
- 값이 없으면 그 말만 빠지고 나머지는 그대로 나간다

실모델 확인: "전북 군산의 숙소 스테이,머뭄은…" weighted 141/500(전 120).
test_social 27 passed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-30 15:43:49 +09:00
3eed628013 merge: feature/admin-full-access(관리자 전체 접근 · 사이트관리 검색 · 화면 썸네일) 을 main 에 병합
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 15:34:21 +09:00
378ad55b5b [feat] solution/backend: 발행·재발행마다 사이트 화면을 찍어 썸네일로 — 실패하면 대표 사진
썸네일이 대표 사진 한 장이라 재발행해도 쇼케이스·목록 카드가 사이트 모습과 달랐다.

- site_thumbnail: Playwright(워커 이미지에 이미 있음)로 /s/<slug> 1280×800 촬영, 실패 시 대표 사진
- THUMBNAIL_CAPTURE=0 이면 촬영을 끈다 — conftest 기본값 0(테스트에서 외부 주소를 열지 않게)

테스트 2건 추가(test_site_thumbnail)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 15:34:21 +09:00
14b6e95f9d [feat] solution/backend,frontend: 관리자가 모든 사업장을 열고 관리 — 사이트관리 검색·업종 필터·삭제, 사장님 더보기 정리
사업장 API 가 전부 "요청자 = 소유자" 로 걸러져서 관리자가 남의 빌더·미니블로그를 열면
PLACE_NOT_FOUND 였다. 운영 중 사업장 정리도 사장님 계정으로 들어가야 했다.

- validator/dependencies: DEVELOPER 가 place_id 경로를 부르면 그 사업장 소유자 신원으로 처리 — [admin-access] 로그
- ops/sites: search(상호·소유자 이메일·이름·아이디) · category 필터, 건수도 같은 조건
- OpsSitesPage: 검색창 · 업종 칩 · 행마다 빌더·미니블로그·삭제(공개 중이면 발행 내린 뒤 삭제)
- SitesPage: 더보기에서 미니블로그 관리 · 예약요청 관리 제거

테스트 4건 추가(test_admin_access). 전체 900 passed · 145 failed(변경 전과 같은 파일: agent_runtime·gemini 등)
frontend tsc·eslint 통과

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 15:34:20 +09:00
87343c791b [feat] solution/backend,frontend,site: 수집·사진 분석·발행 진행을 Teams 로 발송 — 수집 중 배포에도 사진 목록 복구
컨테이너를 재생성하면 docker logs 가 사라져 크롤링이 잘 됐는지 확인할 방법이 없었다.
실측(09-30 버터브루): 수집 도중 배포로 API 가 80초 끊기자 화면이 사진 분석 폴링을
3회 실패 후 포기해 사진 10장이 DB 에 있는데도 0장으로 보였다.

- services/activity_feed.py: 기존 alert_outbox·장애 채널로 kind=activity 이벤트 적재(중복 억제 없음)
- collect_service: 수집 시작 · 채널 크롤링 실패 즉시 · 완료 요약(채널별·fact·사진·누락 필수항목·소요초) · 실패
- vision_service: 사진 분석 결과
- build_service · rollback_service: 첫 발행/재발행/빌드만/되돌리기 시작·끝 — URL · 굽기 사진 미러링 수
- site/prerender.ts: 렌더 보고서에 사진 미러링 수(media) 추가
- frontend pollJob: 연속 3회 실패여도 3분간 무응답일 때만 unreachable
- frontend collectJobs: 사진 분석 폴링을 못 끝내도 사진 목록을 다시 읽고 경고
- test_build_publish: 게이트 반려 검사에서 activity 이벤트는 제외

관련 테스트 17개 파일 295 passed · 2 failed(test_search_console_service — 변경 전에도 실패)
frontend tsc·eslint, site tsc·eslint 통과

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 15:09:52 +09:00
e5153eb66c merge: feat/templates-all-industries(업종 확장 본체) 을 main 에 병합
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 14:58:29 +09:00
fad20e8958 [feat] solution/site,shared: 새 템플릿 8종 업종 확장 — 앞 커밋에서 빠진 본체
앞 커밋(18a2874)에 템플릿 등록과 레이아웃 변경이 빠지고 다른 작업의 새 파일
(services/activity_feed.py, 어디서도 아직 import 하지 않음)이 대신 들어갔다.
같은 작업 폴더를 다른 세션이 함께 쓰고 있었다.

- templates.json: cafe · restaurant · clinic 에 라운드~솔숲 8종
- layouts/*/Rooms.tsx: 객실별 '예약 요청' 버튼은 숙박일 때만, 영문 라벨은 unitWord
- layouts/*/Frame.tsx · Intro.tsx: 이용안내 머리말 · 미니 블로그 설명을 업종 중립으로

eslint·tsc 통과, vitest 123 passed, 33개 사이트 굽기 성공

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 14:58:29 +09:00
8219d3f45e merge: feat/templates-all-industries(새 템플릿 전 업종) 을 main 에 병합
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 14:57:02 +09:00
18a287451e [feat] solution/site,shared: 새 템플릿 8종을 카페·음식점·병원에도 연다 — 객실 문구를 업종별로
새 템플릿을 숙박에만 등록해서 음식점 사장님 화면에는 4종만 보였다.

- templates.json: cafe · restaurant · clinic 에 라운드~솔숲 8종 추가
- kit/unitWord.ts: Room / Menu / Program — 코랄·미니멀·시네마·부티크·빅타이포의 영문 라벨
- 각 Rooms.tsx: 객실별 '예약 요청' 버튼은 숙박일 때만(예약 시트가 숙박에만 있어 누르면 무반응)
- 이용안내 머리말 · 미니 블로그 설명을 업종 중립 문구로, 부티크 탭 'Stay' → 비숙박은 'Guide'
- docs/TEMPLATES.md: 업종별 사용 범위

eslint·tsc 통과, vitest 123 passed, 숙박 9 + 카페·음식점·병원 각 8 = 33개 사이트 굽기 성공 ·
숙박 전용 문구(객실·체크인·Room·예약 요청) 0건 · 간격 검사 0건

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 14:57:02 +09:00
30f8101988 merge: fix/db-url-log(DB 로그 비밀번호 제거) 을 main 에 병합
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 14:07:41 +09:00
2985557838 [fix] solution/backend: DB 엔진 생성 로그에서 비밀번호 제거 — 호스트·포트·DB 이름만 남긴다
백엔드가 뜰 때마다 접속 주소 전체(아이디·비밀번호 포함)를 INFO 로그에 찍고 있었다.
컨테이너 로그를 볼 수 있는 사람이면 누구나 운영 DB 비밀번호를 읽을 수 있었다.

- db_session_manager.create_engine: 읽기·쓰기 로그를 host:port/name 으로

py_compile 통과

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 14:07:40 +09:00
e6a46154b1 merge: feature/template-catalog(숙박 템플릿 8종 · 문서 정리) 을 main 에 병합
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 14:00:12 +09:00
1dc4f5fbf5 merge: origin/main(에이전트 여러 동작 · 사진 도구 · 카톡 승인 알림) 을 feature/template-catalog 에 병합
충돌은 이 브랜치의 주석 정리 커밋(11d30bb)과 main 의 기능 변경이 겹친 백엔드 9개 파일 —
main 쪽을 그대로 썼다(기능이 main 에만 있다). AGENTS.md 는 양쪽 링크를 모두 두고,
DEVLOG 는 요약본 위에 main 의 09-28 에이전트 항목 셋을 붙였다.

site eslint 통과 · vitest 123 passed · frontend tsc·eslint 통과 · 백엔드 py_compile 통과
(백엔드 pytest 는 이 기계에 테스트 DB 계정이 없어 실행하지 못함)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 13:51:53 +09:00
4ff92fb0a6 [docs] docs: 문서 정리 — DEVLOG 요약 · 렌더링 흐름 문서 추가 · 삭제한 방향 문서 링크 정리
DEVLOG 가 1,900줄이 넘어 최근에 무엇을 왜 바꿨는지 찾기 어려웠다.

- DEVLOG.md: 개발 이력상 남길 가치가 있는 항목만 요약
- RENDERING.md: 정적 사이트 · 미리보기 · 발행 세 경우의 흐름과 담당 파일
- README · AGENTS · PRODUCT: 삭제한 DEVELOPMENT_DIRECTION.md 링크 정리(삭제 자체는 앞 커밋),
  admin 은 필요한 규모가 되면 개발한다는 안내, 템플릿 문서 링크

문서만 바꿈

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 13:45:24 +09:00
013cdcfc9f [feat] solution/site,shared: 숙박 템플릿 8종 추가 — 라운드·시네마·빅타이포·부티크·일러스트·코랄·미니멀·솔숲
사장님이 고를 수 있는 숙박 템플릿이 고택 하나뿐이었다. 모두 stay2 콘텐츠(날씨·미니 블로그·
지역 이야기·객실·축제·일정·후기·엽서·노래)를 그대로 싣고 모양만 다르다.

- layouts/<id>/: 템플릿별 Frame·섹션·CSS. 데스크톱은 넓은 배치(1024px 이상), 모바일 우선
- layouts/kit/kit.css: 탭 전환·시트·예약 폼·접기 등 공통 구조
- shared templates.json · builder.ts · catalog.ts: 템플릿·LayoutId 등록
- seo/head.ts: 템플릿별 한글 웹폰트 — 서로 겹치지 않게 배정
- paper Frame·Intro: 같은 탭을 다시 눌러도 맨 위로, 미니 블로그를 글 여러 개로
- docs/TEMPLATES.md · TEMPLATE_DESIGN.md: 목록과 화면 규칙(간격·접기·✓ 표시·글꼴)

eslint·tsc 통과, vitest 123 passed, 템플릿 9개 굽기 성공 · 간격 검사 0건
2026-09-30 13:45:10 +09:00
9d98f2f40b [feat] solution/backend: 카톡이 연결된 사장님에게는 승인 알림을 카톡으로만 — 카톡이 실패했을 때만 메일로 대체
같은 알림이 메일과 카톡으로 두 통 가는 게 번거로워 카톡이 연결돼 있으면 카톡만 보낸다.
채널 친구 아님·차단·Event API 장애로 카톡이 안 나가면 메일로 받치고, 둘 다 못 보내면
SENT 로 표시하지 않아 다음 스윕이 다시 시도한다.
2026-09-30 10:42:03 +09:00
민헌
273efb717f merge: origin/main(카톡 승인 알림 Event API) 을 fix/agent-multi-action 에 병합
충돌은 tests/test_kakao_webhook.py 하나 — 양쪽이 파일 끝에 서로 다른 테스트를 붙였다.
둘 다 살렸다(여러 가게·확인 경로 테스트 → 승인 알림 테스트 순).
channel.py 는 자동 병합 — 저쪽은 approval_notice·approve_post 추가와 _say 선택 인자 확장이라
이쪽의 _pick_place·확인 경로 변경과 겹치지 않는다.

전체 994 passed / 47 failed — 실패 목록은 병합 전과 동일(gemini·openai 키 미설정, search_console, weather_notes)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 09:26:28 +09:00
민헌
3408735290 [docs] docs: 사장님 에이전트 사용 안내 추가 — 말로 할 수 있는 일 목록(AGENT_GUIDE.md)
AGENT.md 는 개발자용 설계 문서라 "왜" 를 다루고, 운영자·CS 가 "카톡으로 무엇을 시킬 수 있나"
를 찾을 자리가 없었다. 무엇을 할 수 있는지만 새 파일에 두고, 동작 근거는 AGENT.md 로 링크한다
(한 사실은 한 곳에).

- docs/AGENT_GUIDE.md: 연결·가게 선택 · 업종별 수정 항목 · 가게 정보 · 섹션 조회/켜기·끄기/옮기기 ·
  사진 · 발행 · 여러 요청 · 할 수 없는 것. 기준 커밋(091d5d9)과 항목 목록의 원본(category_schema) 표기
- docs/AGENT.md: 머리에 사용 안내 링크
- AGENTS.md: 문서 표에 한 줄

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 09:00:27 +09:00
민헌
091d5d9c0e [fix] solution/backend: 에이전트 재검증 결함 정리 — 옮기기 합치기·모르는 방향·낮/밤 시각·만원·첫 번째
추가한 코드를 다시 읽다가 테스트가 못 잡던 여섯 가지가 나왔다. 둘은 조용히 반대로 동작했다 —
"소개 앞쪽으로" 가 소개 뒤로 갔고, "낮 3시" 가 03:00 으로 저장됐다.

- tools: move_section 의 target 제거 — 옮기기는 차례가 뜻이라 "맨 위로, 그리고 한 칸 아래로" 를
  합치면 두 번째가 아니라 원래 자리에서 한 칸 아래가 됐다. 인자까지 같은 것만 합친다
- tools: 못 알아들은 where 는 되묻는다 — 예전 표기(to 만)는 where 가 비었을 때만 읽는다
- tools: 낮은 오후, 밤·새벽 12시는 자정 — '낮 3시' 03:00 · '밤 12시' 12:00 이던 것
- tools: '만원'·'천원'(앞 숫자 없음)을 1로 읽는다. '만 오천원' 은 여전히 되묻는다
- tools: '첫 번째' 순번, toggle 이름 구분에서 '·' 제외('공간 · 좌석 안내' 가 쪼개질 수 있었다)
- runtime: skipped 이름의 줄바꿈·연속 공백을 한 칸으로
- docs/AGENT.md: 값 형식 표 · 옮기기는 합치지 않음 · 모르는 방향은 되묻기

테스트 12건 추가, 에이전트·카카오 186 passed. 전체 965 passed / 47 failed —
실패 목록은 변경 전과 동일(gemini·openai 키 미설정, search_console, weather_notes). pyflakes 새 경고 없음

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 08:42:43 +09:00
e0155d0423 [feat] solution/backend: 카톡 승인 알림에 [승인] 버튼 — 연결된 계정이 권한 (Event API 2-2)
2-1 로 카톡에 글과 [수정하기] 가 오게 됐고, 이제 카톡 안에서 바로 올린다.
승인 권한은 링크가 아니라 연결된 계정이다 — 버튼이 들고 온 글 ID 는 믿지 않는다.

- kakao_bot: 알림 카드에 [승인](action: block, blockId=KAKAO_APPROVE_BLOCK_ID,
  extra={kind:approve, post_id}) 을 그리고, 클릭은 action.clientExtra 로 받는다.
  블록 ID 가 비면 버튼을 그리지 않는다(눌러도 안 되는 버튼을 보내지 않는다)
- channel.approve_post: 누른 발화자 → 사장님 → 그 가게의 미처리·기한 전 글인지 재확인 후
  approve_by_owner(메일·'바로 발행' 과 같은 경로라 재발행 잡·쓰레드 공유까지 동일).
  연결 안 됨·남의 글·이미 올림·기한 지남은 같은 안내로 끝나고 두 번 올라가지 않는다.
  답장은 "올렸습니다" + [사이트 보기](#blog). LLM 을 부르지 않는다
- 5초를 넘겨도 승인 작업은 취소하지 않는다(asyncio.shield) — 여러 번 커밋하는 작업이라
  중간에 끊기면 승인만 되고 재발행이 안 걸린 글이 남는다
- PostService._blog_url → blog_url(채널이 [사이트 보기] 에 쓴다)

test_kakao_webhook 10건 추가(구현 전 5건 실패 확인), 관련 11개 스위트 239 passed.
실제 클릭 본문(action.clientExtra 위치)은 첫 클릭의 로그로 확인한다.
2026-09-29 16:50:40 +09:00
f1b473968d [feat] solution/backend: 승인 알림을 카톡으로도 발송하고 웹훅이 메시지를 그린다 (Event API 2-1)
결정(2026-09-29): 카톡과 메일 둘 다, 승인은 링크가 아니라 연결된 계정 신원으로.
이 커밋은 발송과 메시지 그리기까지고 [승인] 버튼은 다음 단계(2-2)다.

- blog_jobs._send_one: 메일에 더해 Event API 로 보낸다. 하나라도 나가면 SENT,
  아무 데도 안 나가면 SENT 로 표시하지 않아 다음 스윕이 다시 시도한다. 카톡은 채널
  친구가 아니거나 차단했으면 실패하므로 메일을 빼지 않는다. send_now 도 같은 경로
- 카톡 params 로 post_id 와 수정용 일회용 코드(edit_token)를 넘긴다. 코드 평문은
  발송 시점에만 알아서다. 로그에는 params 의 키만 남기고 값은 남기지 않는다
- channel.approval_notice: 발화자 키 → 사장님 → 그 글이 그 사장님 가게 것·미처리·
  기한 전일 때만 본문과 [수정하기] 를 준다. 연결 안 됨·남의 글·처리됨·만료·이상한
  ID 는 구분 없이 같은 안내(구분해 주면 글 ID 를 탐색할 수 있다)
- kakao_bot: userRequest.params.post_id 가 있으면 승인 알림 요청으로 처리하고 링크
  버튼은 본문과 따로 textCard 로 그린다(카드 설명 길이 제한을 피한다)
- KAKAO_APPROVAL_PUSH_ENABLED(기본 0), KAKAO_APPROVAL_EVENT_NAME 추가 —
  오픈빌더 이벤트 블록(스킬 연결)과 배포가 끝나기 전에는 켜지 않는다

test_blog_owner 6건·test_kakao_webhook 6건 추가, 카카오·미니블로그 스위트 119 passed,
인접 스위트 110 passed. 실제 카톡 수신은 콘솔 설정·운영 배포 뒤에 확인한다.
2026-09-29 16:13:07 +09:00
민헌
36b818eda3 [feat] solution/backend: 대화로 섹션 옮기기·숨기기 확장 — 앞·한 칸·N번째·자리 바꾸기 · 여러 개 끄기 · 꺼진 목록
"맨 위 · 맨 아래 · X 다음으로" 만 되고 "X 앞으로" "한 칸 위로" "세 번째로" "자리 바꿔줘" 는
거절됐다. 발행본(HomePage.tsx)은 히어로를 늘 맨 위, SNS 를 늘 맨 아래에 그리는데 대화는
그 둘을 옮기고 "옮겼습니다" 라고 답했다 — 화면은 그대로였다.

- tools: move_section 에 where(맨 위·맨 아래·앞·뒤·위로·아래로·번째·바꾸기)·count —
  where 가 비면 예전 to 표기로 읽는다. 한 칸·N번째는 보이는 순서로 센다(꺼진 부분과 자리만
  바꾸는 헛이동 방지). 없는 순번·꺼진 부분의 칸 이동은 거절, 이미 그 자리면 Unchanged
- tools: PINNED(히어로 맨 위 · SNS 맨 아래) — 옮기기와 그 둘을 기준으로 한 앞·뒤·바꾸기를 막는다.
  히어로 다음은 맨 위, SNS 앞은 맨 아래로 읽는다
- tools: toggle_section 이 쉼표로 여럿을 받는다 — 하나라도 못 찾거나 잠겼으면 아무것도 안 바꾼다
- tools: list_sections 는 보이는 순서에 번호(= N번째 기준)를 붙이고 꺼진 것을 모은다, only=꺼진
- prompts/runtime: 스키마에 where·count·only, 섹션 줄에 [항상 맨 위]·[항상 맨 아래] —
  목록은 tools.PINNED 하나를 runtime 이 넘긴다(prompts 는 services 를 import 하지 않는다)
- docs/AGENT.md: 옮기기·숨기기 표와 근거. 동작하지 않던 예시("후기 빼줘") 교체 —
  이용 후기는 섹션 목록에 없고 발행본이 늘 그린다

테스트 21건 추가, 에이전트·카카오 174 passed. pyflakes 새 경고 없음

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 15:49:20 +09:00
983c05b3c3 [feat] solution/backend: 카카오 챗봇 Event API 클라이언트와 테스트 발송 스크립트 (승인 알림 카톡 발송 1단계)
미니블로그 승인 알림을 메일에 더해 연결된 카카오톡으로도 보내기 위한 첫 단계.
승인 방식은 인라인 버튼 + 연결된 계정 신원 확인으로 정했고(링크가 없어 메신저
미리보기가 먼저 열어 승인되는 문제가 없다), 이 커밋은 발송 통로와 규격 확인까지다.

- services/external/kakao_event.py: POST bot-api.kakao.com/v2/bots/{botId}/talk.
  예외 문구(str)에는 키·발화자 ID·응답 원문을 넣지 않고 진단용은 detail 에만 둔다
- KAKAO_BOT_REST_API_KEY 를 기존 KAKAO_REST_API_KEY(카카오 로컬 API)와 갈랐다 —
  Event API 는 채널을 연결한 비즈니스 인증 앱의 키를 써야 해서 앱이 다를 수 있다
- KAKAO_EVENT_DEV=1 이면 봇 ID 뒤에 "!"(개발 채널). KAKAO_BOT_ID 자체는 웹훅이
  bot.id 대조에 쓰므로 고쳐 쓰지 않는다
- scripts/kakao_event_send_test.py: 실제 카톡으로 한 건 보내 규격을 확인하는 스크립트

실제 발송으로 확인함(요청 성공 + 카톡 수신). 이벤트 미배포 시 "Invalid Event name" 404 를
돌려주는 것도 확인했다.

test_kakao_event.py 7건, 카카오·설정 관련 스위트 75 passed
2026-09-29 15:25:06 +09:00
민헌
4b48be7e81 [chore] solution/backend: 에이전트 테스트의 죽은 도우미 user_of 삭제 — 없는 함수를 import
user_of 는 처음 들어온 커밋(b1a34ba)부터 한 번도 호출되지 않았고, import 하는
decode_access_token 은 어느 브랜치 기록에도 정의된 적이 없다 — 부르는 순간 ImportError 다.
같은 일은 DecodeAccessToken 을 쓰는 _sections 가 이미 한다.

- tests/test_agent_runtime.py: user_of 삭제(호출처 0 — 레포 전체·문자열 참조까지 확인)

pyflakes 경고 없음, test_agent_runtime 84 passed

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 15:13:44 +09:00
민헌
aaf97f2797 [fix] solution/backend: 대화 수정의 값 형식 검증 · 여러 요청의 못 한 것·남은 것 안내 — 조용히 틀리던 경로 정리
말로 고친 값이 형식 검증 없이 저장됐다 — "반려동물 이제 돼요" 가 "가능" 으로 들어가면
화면엔 "가능" 이 뜨는데 factBool 은 'true' 만 참으로 읽어 구조화 데이터가 거짓이 된다.
여러 요청을 한 번에 받을 때도 할 수 없는 것·멈춘 뒤의 것이 말없이 사라져 사장님은
전부 된 줄 알았다(message 는 actions 가 있으면 버려졌다).

- tools: set_fact 값을 스키마 형식으로 맞춘다(bool true/false · time HH:MM · number 숫자) —
  "3시" 처럼 오전·오후를 모르면 저장하지 않고 되묻는다. 알림 문구는 가능·불가로 말한다
- tools: enabled 를 모르면 끄지 않고 되묻는다 — "" 를 끄기로 읽어 "다시 보여줘" 가 섹션을 껐다
- tools: 대표 지정·사진 목록·프롬프트를 '나가는 사진' 기준으로 — 내린 사진을 대표로 지정하고
  "바꿨습니다" 라고 하던 것. 정확히 맞는 이름을 부분 일치보다 먼저 고른다
- tools: Unchanged 표시 — "이미 켜져 있어요" 에 재발행 안내·카톡 발행 대기가 붙던 것
- tools/runtime: 인자가 문자열·dict 가 아니어도 죽지 않는다(모델의 스키마 위반)
- runtime: 같은 대상은 마지막 하나로 합친다(Tool.target, 상한 세기 전). 멈춘 뒤 남은 요청과
  지어낸 도구를 코드가 만든 이름으로 알린다. 발행 확인 문구는 맨 끝에 선다
- prompts: 항목마다 형식을 싣고 skipped(할 수 없는 요청의 이름) 칸 추가 — 문장은 런타임이 만든다.
  다른 가게 이야기면 되묻는 규칙
- channel: 발화에 다른 내 가게 이름이 나오면 모델을 부르기 전에 고르게 한다. 기억한 가게가
  목록에 없으면 비우고 목록으로. 확인 경로의 AgentError 를 사장님 말로 옮긴다
- docs/AGENT.md: 값 형식 · 못 한 것·남은 것·겹친 것 · 나가는 사진 기준

테스트 48건 추가, 에이전트·카카오 153 passed. 전체 932 passed / 47 failed —
실패 47건은 변경 전과 동일(gemini·openai 키 미설정, search_console KeyError, weather_notes)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 15:11:00 +09:00
86d870bab5 [feat] solution/frontend: 랜딩 하단에 사업자 정보 표기 — showBusinessInfo 로 랜딩에만 2026-09-29 13:33:40 +09:00
7f6f16f307 [fix] solution/site: 고택 — 날씨를 첫 화면 바로 아래로 · 예약 요청 창은 달력부터 펼친다
섹션 순서가 업종 기본(날씨 맨 뒤)을 따라가서 stay2 와 달리 날씨가 맨 아래로 갔고,
예약 요청 창은 '날짜 · 시간 선택' 을 접어 두어 손님이 한 번 더 눌러야 달력이 보였다.

- paper/Hero: 날씨 섹션이 켜져 있으면 첫 화면 바로 뒤에 그린다(레이아웃의 weather 자리는 비움)
- paper/BookingForm: 날짜·시간 칸을 처음부터 펼친다
- app.test: 날씨를 섹션 목록 맨 뒤에 둬도 첫 화면 바로 다음에 나오는지

site tsc·eslint·vitest 107 passed, 로컬 발행(stayyyy v3)에서 순서와 예약 창 확인

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-28 17:05:39 +09:00
c4aa1794e2 [feat] solution/backend: 한 발화에 여러 가지 — 응답을 actions 배열로
사장님은 "체크인 3시로 바꾸고 후기도 빼줘" 처럼 한 번에 시킨다.

- prompts/agent: 응답 스키마를 actions 배열로
- runtime: 시킨 순서대로 실행. MAX_ACTIONS=5 — 무한정이면 "다 지워줘" 한 마디에
  연쇄 실행된다
- ★ publish(SEMI)가 섞이면 그 앞까지만 하고 확인을 받는다. 확인이 필요한 행위를
  다른 일에 묻어 실행하면 확인의 의미가 없다
- ★ 중간에 실패해도 앞의 것을 되돌리지 않는다(사장님 결정). 되돌리는 것도 시키지
  않은 변경이다 — 대신 무엇이 됐고 무엇이 안 됐는지 그대로 말한다
- Tool.republish 플래그로 재발행 안내를 런타임이 한 번만 붙인다. 도구 문장에
  박아 두면 셋을 고쳤을 때 같은 말이 세 번 나왔다

실모델 4/4 정확히 쪼갬(1.8~2.8초).
test_agent_runtime·test_kakao_webhook 63 passed.
전체 878 passed / 53 failed — 53 은 기존과 동일. npm run lint 통과

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-28 17:00:30 +09:00
0a0ae4e054 [fix] solution/site: 첫 화면 문구가 따로 있으면 한 줄 요약이 화면에 없어 발행이 막히던 것
첫 화면은 tagline → heroSubline → summary 중 하나만 쓰고, 이용안내는 intro fact 를 먼저 쓴다.
둘 다 있으면 JSON-LD description(summary)이 화면에 없어 구조화 데이터 대조에서 굽기가 실패했다.

- AboutSection: 첫 화면이 다른 문구를 쓸 때만 제목 아래에 summary 한 줄
- app.test: tagline·intro fact·summary 가 다 있는 payload 로 simple·paper 모두 화면에 summary 가 있는지

재현 테스트 실패 확인 후 수정, site vitest 106 passed, 머뭄 payload 심플 굽기 통과

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-28 16:10:37 +09:00
7941e5a9f6 [feat] solution/site: 고택 노래 목록 상자를 종이 톤으로 — 가는 선 · 명조 제목 · 흙색 판
stay2 는 머리글 아래로 펼치는 목록이지만 떠 있는 상자가 쓰기 낫다(대표 결정). 모양만 고택에 맞춘다.

- paper.css: #w4d-panel 배경 --paper · 1px --line · 4px 모서리 · 옅은 그림자
- 줄 구분 실선, 지금 곡은 --soft 바탕, 판 라벨·이퀄라이저 #8c4a2f, 가사 명조

site vitest 105 passed, 390px 에서 목록을 펼쳐 확인

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-28 16:06:44 +09:00
456a7c288b [docs] docs: DEVLOG 중복 항목 제거 — 09-23·09-28 이 두 번 들어가 있었다
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-28 16:05:38 +09:00
765b9b6463 [feat] solution/site: 고택(paper) 레이아웃을 /s/stay2 시안과 같게 — 탭 4개 · stay2 마크업 이식
고택이 stay2 DOM 이식 1차에 머물러 화면 전체 폭·다른 머리글로 나가 시안과 달랐다.

- layouts/paper/paper.css: stay2 CSS 를 .w4p 안으로 옮겼다(@layer components, Tailwind 유틸이 이긴다)
- layouts/paper: 머리글·탭·첫 화면·날씨·블로그·소개·객실·사진·영상·주변·예약·이용안내·오시는 길·FAQ·축제·일정·지역 이야기·예약 요청 창
- stay2 하위 페이지(지역 소개·이용안내·이야기)는 #/area · #/stay · #/story 탭 전환, 내용은 한 HTML 에 둔다
- 공용 섹션에서 계산 함수만 꺼내 쓴다: infoView · useBookingRequest · StayBookingDemo 달력 함수
- SectionList: 레이아웃이 blog · reviews · postcardMaker 자리를 바꿀 수 있다
- templates.json paper: 보조색 #5b564e · 본문 고딕 · 모서리 4px · 간격 2.5rem(stay2 값)
- app.test: 고택 Frame 표식을 새 클래스(w4p)로

머뭄 데이터로 구워 1280·390px 에서 stay2 와 나란히 비교(첫 페이지 4855/4857px).
site tsc·eslint·vitest 105 passed, frontend vite build 통과

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-28 16:05:21 +09:00
11d30bb3d1 [chore] solution,admin,ontology: 코드 주석을 한 줄로 — 히스토리 주석 삭제
여러 줄 주석이 설명보다 경위(예전·실측·지적)를 적고 있어 읽는 사람이 결론을 찾기 어려웠다.

- ts·tsx·js·mjs·css·py 478개: 여러 줄 주석은 첫 문장 한 줄로, 과거형·날짜 문장은 삭제
- 주석 위치는 TypeScript 파서·파이썬 tokenize/ast 로 찾는다 — 문자열 안의 # · /* 는 건드리지 않는다
- eslint·ts·noqa·type: ignore 같은 지시 주석은 그대로 둔다

파이썬 275개 정리 전후 AST 동일, TS 298개 주석 뺀 토큰 동일(빈 JSX 주석 10곳만 차이).
site·frontend·admin tsc, site vitest 105 passed

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-28 16:05:19 +09:00