o2o-site-AEO/docs/DEVLOG.md
Mina Choi 23162783f9 [feat] solution/frontend: 붙여넣기 아이템 셋 — 가요 다방·오늘의 한 장·반나절 산책
gunsan_365_story_db.xlsx 365행을 뜯어 보니 고유 주제는 52개이고 한 주제가 7회씩 돈다
(접미사 10개만 회전). 날짜 축으로 카드를 늘어놓으면 이레마다 같은 카드가 돌아온다 —
그래서 묶는 축을 주제로 잡고 날짜는 일력 한 장에만 썼다. 같은 파일 DB_Guide 가 가사·원문
전재를 금지해 가요 스키마에 lyrics 필드를 아예 두지 않았다. 없는 칸은 채울 수 없다.

- canvas/dataSpec.ts: 스키마·예시·프롬프트 단일 표. 레지스트리와 같은 결이라 한 줄을 더하면
  캔버스·[콘텐츠] 탭·프롬프트가 동시에 는다. 파싱은 절대 throw 하지 않는다 — 편집 중인 JSON 은
  늘 깨져 있고 그때 캔버스가 죽으면 고칠 방법이 없다
- shared/types/builder: SectionItem.data 는 파싱본이 아니라 원문 문자열이다. 파싱본만 들면
  JSON 이 깨진 순간 사장님이 쓴 걸 잃는다
- variants/{songs,daily,course}: 턴테이블·일력·승차권. 카드 격자를 쓰지 않고 전부 가로로 넘긴다
- RightTabsPanel: JSON 칸 + 프롬프트 복사/보기·예시 넣기·줄맞춤. 프롬프트에 상호와 주소에서 뽑은
  시·군·구를 박아 내보낸다 — 빈칸을 남기면 못 채우고 그대로 보내고 모델이 엉뚱한 곳을 지어낸다
- dataSpec.locate: JSON.parse 오류가 두 형식이다. position 형만 보면 조각 인용 형에서 위치를
  통째로 잃는다 — 조각을 원문에서 되찾아 센다
- SECTION_DATA_MAX_CHARS: 테마 상한 64KB(site_service._THEME_MAX_BYTES)를 세 섹션이 함께
  넘길 수 있고 거절은 발행 직전에야 드러난다. 화면에서 먼저 끊는다
- industryData: 네 업종에 꺼진 채로 넣는다. 내용 없이 켜져 있으면 발행본에 빈 섹션이 나간다
- index.html: Gugi·Gowun Batang·Nanum Pen Script. 없으면 고딕으로 떨어져 감성이 사라진다

발행 사이트(solution/site)는 아직 variantId 도 data 도 읽지 않는다 — 지금은 빌더 캔버스 전용이다.

tsc·eslint·vite build 통과(frontend·admin), 세 배리에이션 SSR 렌더 확인, 파서 경계 12건 확인
2026-09-01 17:58:58 +09:00

5.1 KiB

개발 일지

무엇을 왜 바꿨는지 날짜순으로 남긴다. 새 항목을 위에 추가한다. 결론과 배경은 각 문서가 단일 출처다 — 여기에는 요약과 링크만 둔다.


2026-09-01 — 붙여넣기 아이템 셋: 가요 다방 · 오늘의 한 장 · 반나절 산책

한 일

  • 섹션 타입 3개 추가(songs · daily · course). 데이터가 수집(fact)이 아니라 사장님이 붙여넣은 JSON 에서 온다 — 새 갈래다.
  • canvas/dataSpec.ts 신설: 스키마·예시·프롬프트가 한 표에 모인다. 배리에이션 레지스트리와 같은 결이라 여기 한 줄을 더하면 캔버스·[콘텐츠] 탭·프롬프트가 동시에 는다.
  • SectionItem.data?: string 추가. 파싱본이 아니라 원문 문자열을 담는다.
  • [콘텐츠] 탭에 JSON 칸 + [프롬프트 복사] [프롬프트 보기] [예시 넣기] [줄맞춤].
  • 업종 시드 넷 모두에 세 섹션을 꺼진 채로 넣었다.

왜 이 모양인가 gunsan_365_story_db.xlsx(365행)를 분석한 결과 고유 주제는 52개고 한 주제가 7회씩 돈다 (접미사 10개만 회전). 날짜 축으로 카드를 늘어놓으면 이레마다 같은 카드가 돌아온다 — 그래서 묶는 축을 주제로 잡고, 날짜는 일력 한 장에만 썼다. 같은 시트 DB_Guide가사·현대문학 원문 전재를 금지해서 가요 스키마에 lyrics 필드를 아예 두지 않았다. 없는 칸은 채울 수 없다.

밟은 함정

  • 테마 상한 64KB(site_service._THEME_MAX_BYTES). 세 섹션이 각자 JSON 을 채우면 넘고, 거절은 발행 직전에야 드러난다. → SECTION_DATA_MAX_CHARS(12,000자)로 화면에서 먼저 끊는다.
  • JSON.parse 오류 메시지가 두 형식이다. position N (line L column C) 형과, 위치 없이 깨진 조각만 인용하는 형. 앞의 것만 보면 후자에서 위치를 통째로 잃는다 — 조각을 원문에서 되찾아 센다.
  • 파싱은 절대 throw 하지 않는다. 편집 중인 JSON 은 늘 깨져 있고, 깨진 순간 캔버스가 죽으면 못 고친다.

아직 안 한 것

  • 발행 사이트(solution/site)는 variantIddata 도 아직 안 읽는다. 지금은 빌더 캔버스 전용이다.
  • 프롬프트는 상호·주소를 박아 내보낸다(빈칸을 남기면 사장님이 못 채우고 그대로 보낸다).

검증tsc --noEmit · eslint · vite build 통과(frontend·admin). 세 배리에이션 SSR 렌더 확인, 파서 경계 12건 확인.


2026-09-01 — 설정을 .env 하나로 모았다

한 일

  • 백엔드 설정을 toml → pydantic-settings(FastAPI 공식 방식)로 옮겼다.
  • config_loader.py · config.local.toml.example · config.test.toml.example 삭제.
  • server_configs.py 107줄 → 26줄. _apply_*_env_override 함수 4개 제거.
  • 호출부 21개 파일은 안 건드렸다 — 같은 이름을 그대로 내보낸다.

키마다 if os.environ.get(...) 를 손으로 나열하는 구조였다. 하나 빠뜨리면 조용히 틀리는데, 실제로 client_url 이 빠져 있어 배포 주소의 API 호출이 전부 CORS 로 막혔다. BaseSettings 는 필드를 선언하면 환경변수가 자동으로 들어와 이 사고가 구조적으로 안 난다.

하는 김에 잡은 잠재 버그

  • .env 경로가 세 단계라 solution/.env(없는 파일)를 보고 있었다. 백엔드를 solution/ 아래로 옮길 때 안 고쳐진 자리다. toml 이 값을 들고 있어 로컬에서 안 드러났고, 도커는 compose 가 환경변수를 직접 넣어 역시 멀쩡했다. toml 을 없앤 지금은 유일한 공급원이라 치명적이었다.
  • 환경변수 이름을 validation_alias 로 못 박았다. 안 그러면 port 필드가 흔한 PORT 를 주워 먹어 엉뚱한 포트로 뜬다.

결과 — 백엔드 설정 파일은 최상위 .env 하나뿐이다. → DECISIONS.md


2026-08-31 — 킹서버 최초 배포

한 일

  • ~/data2/o2o-web4ai 에 배포. DB(web4ai_db) 생성 + init.sql 적용.
  • 컴포즈 포트를 전부 .env 변수로 뽑았다. 로컬 기본값은 그대로다.
  • deploy.sh · log.sh 추가.

왜 포트를 뽑았나 킹서버는 :80 을 호스트 nginx 가 이미 물고 있다. 사내망에 열려 있는 건 30xxx 대역뿐이라 그 안에서 자리를 잡아야 했다. → SERVERS.md

밟은 함정

  • PUBLIC_API_BASE_URL브라우저가 부르는 주소다. localhost 로 두면 화면은 뜨고 API 만 죽는다 — 콘솔을 열기 전엔 안 보인다.
  • 내부 화면의 "빌더 열기" 가 VITE_SOLUTION_URL 미주입으로 죽은 링크였다. 로컬에서는 기본값이 맞는 주소라 서버에 올리기 전까지 드러나지 않았다.
  • deploy.sh api 는 worker·api-admin 도 함께 갈아끼운다. 셋이 이미지 한 벌을 나눠 쓰는데 하나만 바꾸면 옛 코드로 도는 컨테이너가 남고, ps 로는 셋 다 살아 있어 구분이 안 된다.

남은 것w4ai.o2o.kr DNS + 앞단(59.14.81.3) 포워딩. 서버에 sudo 가 없어 인프라 몫이다.