← Home

TAB 대시보드·/ask 다국어 버그 수정 세션 로그 (2026-08-19)

Original Content

문서 정보

작업일: 2026-08-19 · 작업 볼트: 04_GuWiki(사이트 소스는 quartz-site, 대시보드 소스는 05_SalesReport) · 작업 도구: Claude Code (Sonnet 5) · 최종 산출물: functions/api/ask.js/sync-queries.ps1/rebuild-site.ps1/generate-answers.ps1 수정 + TAB_Dashboard.html 4라운드 수정, 배포 완료 이 문서는 나중에 GuWiki Ingest·log.md 작성용 원본 자료로 쓰기 위한 세션 캡처입니다. 상세 diff·검증 로그는 log.md의 같은 날짜 항목들에 이미 기록돼 있으므로, 이 문서는 배경·근본 원인·재사용 가능한 교훈 위주로 압축한다.

1. /ask 비한국어 질문 처리 버그 3건

배경

turboairbrain.uk/ask는 방문자 질문 언어 그대로 Claude가 답변하는데(다국어 방문자 대응 목적), 그 결과가 볼트에 동기화되고 사이트 “답변보기”에 재게시되는 두 지점에서 문제가 발생했다:

  1. 볼트 가독성: 한국어만 읽는 운영자(David)가 비한국어(중국어 사례 실측)로 등록된 질문·답변을 이해할 수 없음.
  2. 사이트 언어 스위처 무반응: KR/CN/EN 버튼이 있지만 번역 인프라 자체가 없어 눌러도 항상 원래 언어 그대로.
  3. 제목 생성 버그: “답변보기” 목록 제목이 질문과 무관하게 “답변”/“참고 문서”/“参考文档” 같은 값으로 뽑힘 — Get-AnswerHeading이 ## Answer 섹션의 첫 heading을 무조건 채택했는데, ask.js가 답변 끝에 항상 강제하는 ”# 참고 문서” 섹션이 그 자리를 차지해버림.

해결 — “숨긴 아카이브 블록” 패턴

별도 번역 API 없이 Claude 자신에게 답변 생성과 동시에 번역을 겸하게 함 — 시스템 프롬프트에 답변 끝 <!--ARCHIVE_START-->...<!--ARCHIVE_END--> 블록(한국어 제목/질문/답변 + 제3언어 제목/질문/답변)을 추가하도록 지시하고, ask.js가 방문자 응답 전에 이를 파싱·제거. sync-queries.ps1이 이 필드들로 displayTitle/displayTitleZh/En/displayQuestionZh/En/language/summaryLanguage frontmatter와 ## 한국어 요약 섹션, i18n-src/answers/*.md 번역 소스를 자동 생성. generate-answers.ps1엔 제목 오탐 방지용 차단목록($genericHeadings)을 추가. 핵심 실수와 정정: 처음엔 query: frontmatter를 원문 그대로 뒀는데, 사이트 KR(기본) 상태가 이 값을 그대로 노출한다는 걸 배포 직전에 재확인 — questionKo로 교체해야 KR 버튼에서도 제대로 읽힘. 상세 패턴: Cloudflare Pages Function + Claude API로 Wiki 기반 Q&A 봇 만들기 §9.

기존 20건의 askedVia: turboairbrain.uk/ask 문서도 소급 전수조사해, $meta 커버리지가 없던 2026-08-19 신규 7건 중 6건에 displayTitle 직접 핀, 유일한 실제 비한국어 사례(중국어, Panasonic 가격비교 질문)는 전체 번역 처리(한국어 요약 + 3개 언어 frontmatter + i18n-src 본문 파일).

2. TAB_Dashboard.html 4라운드 수정

라운드 1 — 브랜치 합계 카드 + 라인 레이블 하단 전환 + 브랜치 비교 차트 고침

  1. “오늘 브랜치별 현황”에 맨 왼쪽 “합계” 카드 추가(그리드 3→4열) — 이미 계산된 NSW/VIC/QLD stats를 reduce()로 합산만, 새 집계 경로 없음. 카드 색은 브랜치색이 아니라 중립 slate.
  2. 막대차트의 “여백 부족 시 안쪽 전환”(smartBarDatalabels, 기존)과 대칭되는 smartLineDatalabels() 신설 — 라인 포인트가 차트 상단에 너무 가까우면 align:'top' 대신 'bottom'.
  3. “실적 종합” 탭의 “브랜치 비교”(당시 “NSW/VIC 비교”) 토글이 무반응이던 버그 수정 — compareMode가 f.region === 'NSW_VIC_QLD'로만 하드코딩돼 있었는데 그 페이지의 지역 필터 기본값은 NSW_VIC라서 기본 상태에서 토글이 항상 no-op이었음. “동월 비교” 차트가 쓰던 MULTI_BRANCH_SETS(지역→브랜치배열 매핑)를 모듈 공유 상수로 승격해 재사용, buildTrendSeriesByState(NSW/VIC 하드코딩)를 buildTrendSeriesByBranches(브랜치 배열 인자, 몇 개든 대응)로 일반화.

라운드 2 — 라인 레이블 후속 버그 (같은 날 재보고)

배포 직후 사용자가 스크린샷으로 재확인: “동월 비교” 콤보차트의 정점(최고값) 라인 포인트의 값 레이블이 위/아래 어디에도 안 보임. 원인 추정: smartLineDatalabels()의 픽셀 거리 기반 판정만으로는 Chart.js 레이아웃 계산 타이밍이나 플러그인 내부 충돌 감지(display:'auto')와 상호작용해 간헐적으로 놓침. 해결: 값 기반 판정을 이중 안전장치로 추가 — “이 데이터셋 안에서 최고값(정점)인 포인트인가”는 축 스케일링 여유폭과 무관하게 항상 정확하므로, isDatasetPeak()이 참이면 픽셀 판정과 무관하게 무조건 align:'bottom'. 교훈: 픽셀 기반 런타임 판정보다, 원본 데이터만으로 계산 가능한 조건이 있다면 그쪽을 우선하거나 최소한 이중 안전장치로 병행하는 것이 안전.

라운드 3 — 전 페이지 지역(Branch) 필터 기본값 통일

그동안 “오늘의 현황” 페이지의 todayYoyBranch만 NSW_VIC_QLD가 기본이고 나머지 탭이 공유하는 사이드바 지역 필터(defaultFilterState().region)는 옛 “TA QLD 제외” 토글의 기본 동작(checked)을 물려받아 NSW_VIC로 남아 있던 것을 NSW_VIC_QLD로 통일 — 한 함수(defaultFilterState)만 고치면 today를 제외한 전 탭에 반영되는 구조라 수정 자체는 단순했지만, HTML <select>의 selected 속성도 같이 옮겨야 최초 페인트 시 어긋나지 않는다는 점이 재확인됨(§9.1의 “JS 기본값과 HTML selected는 항상 짝을 맞출 것” 원칙 재적용).

재사용 가능한 교훈 (요약)

  • 다국어 자동생성 콘텐츠: 원본을 만드는 LLM 호출 자체에 “방문자에게 안 보이는 메타 블록”을 곁들이는 게 별도 번역 API보다 싸고 지연도 없다. 단, “기본 언어” 슬롯을 원문이 아니라 번역본으로 채우는 것을 잊으면(이번 세션의 실수) 스위처가 절반만 작동한다.
  • Chart.js 데이터라벨 위치 버그: 픽셀 기반 런타임 판정(chartArea/getDatasetMeta 좌표 계산)은 직관적이지만 레이아웃 타이밍·플러그인 내부 로직과 얽히면 간헐적으로 실패할 수 있다. 원본 데이터로 계산 가능한 조건(예: “이 값이 데이터셋의 최댓값인가”)이 있다면 그것을 우선하거나 이중 안전장치로 병행하는 편이 안전.
  • 여러 UI 요소가 같은 “기본값” 개념을 공유할 때: 하드코딩된 조건(f.region === 'NSW_VIC_QLD')이 아니라 공유 매핑(MULTI_BRANCH_SETS)을 통해 판정하면, 나중에 기본값이나 옵션 집합이 바뀌어도 조건문을 다시 찾아 고칠 필요가 없다 — 이번 세션에서 바로 이 하드코딩이 “브랜치 비교” 버그의 근본 원인이었다.