MyGPT - DefaultFontSize가 먹히지 않은 이유

추석 명절을 보내고 가족들을 배웅한 뒤, 미뤄둔 블로그 글을 정리하려고 MyGPT를 실행했다. 그런데 화면을 보자마자 뭔가 어색했다. 평소보다 답변 본문과 입력창의 글자가 커져 있었다. 단순히 보기 불편한 정도라면 그냥 넘길 수도 있었지만, 폰트가 커지면서 한 화면에 표시되는 내용이 눈에 띄게 줄었다. 긴 대화와 기술 자료를 정리할 때는 스크롤 횟수가 늘어나고, 전체 흐름을 한눈에 보는 것도 불편해졌다.

 

MyGPT 쪽 설정을 바꾼 기억은 없었다. 기존처럼 QtWebEngine의 DefaultFontSize=14, zoom 1.0을 사용하고 있었다. 그렇다면 MyGPT가 아니라 ChatGPT 웹 페이지 쪽에서 무언가 바뀐 걸까.

QtWebEngine 기본 폰트 한계

먼저 MyGPT 자체 설정부터 다시 확인했다. 소스에는 여전히 DefaultFontSize=14가 적용되어 있었고, 초기 zoom도 1.0이었다. CSS를 삽입하거나 DOM을 수정하는 코드도 없었다. 처음에는 전체 zoom을 한 단계 줄여보기도 했다. ⌘ + -로 0.9까지 내리면 화면 밀도는 다시 익숙한 수준으로 돌아왔다. 문제는 한글 IME였다. 예전에 이미 겪었던 것처럼 조합 중인 문자열 아래에 underline이 거슬리게 나타났다. 이 문제 때문에 과거에도 zoom 대신 Qt의 기본 폰트 크기를 14로 맞췄던 기억이 있었다.

 

결국 이번 문제는 zoom으로 덮을 일이 아니라, 실제로 어느 영역이 몇 px로 렌더링되는지를 확인해야 했다.

Computed Style 확인

Codex에 진단 코드를 맡겨 QtWebEngine에서 실제 computed style을 읽었다. 결과는 생각보다 명확했다.

  • DefaultFontSize: 14
  • zoomFactor: 1.0
  • html: 14px
  • body: 14px
  • sidebar: 14px
  • assistant 본문: 16px
  • composer: 16px
  • assistant block code: 12px
  • user block code: 16px

Qt의 기본 폰트 설정이 무시된 것은 아니었다. html과 body, sidebar에는 실제로 14px이 적용되고 있었다. 문제는 ChatGPT가 각 영역에 자체 typography 규칙을 지정하고 있다는 점이었다. assistant 본문에는 --markdown-font-size, composer에는 --codex-chat-font-size, assistant code에는 --codex-chat-code-font-size가 적용되고 있었다. user code는 별도의 code 크기 토큰 없이 부모의 16px을 1em으로 그대로 상속했다.

 

즉, DefaultFontSize=14는 브라우저 기본값일 뿐이었다. 페이지 CSS가 16px을 명시하면 Qt의 기본값보다 페이지 규칙이 우선한다.

DOM 오인

처음부터 정확하게 잡힌 것은 아니었다. assistant 본문을 찾기 위해 넣은 진단 heuristic이 실제 답변 문단이 아니라 composer 내부의 p.placeholder를 잡아버렸다. 이 상태에서는 본문이 16px이라는 결론을 내려도 근거가 틀릴 수 있었다. 그래서 selector를 계속 추측하는 대신 현재 ChatGPT의 runtime DOM 구조를 직접 덤프했다. 여기서 기존에 기대했던 article 중심 구조나 data-message-author-role="assistant" 같은 형태가 아니라 다른 semantic attribute들이 사용되고 있다는 것을 확인했다.

 

assistant 본문은 data-markdown-text-style="assistant-message", user message는 data-user-message-bubble, code block은 data-markdown-copy="code-block"을 기준으로 구분할 수 있었다. 난수 형태의 class 이름을 쫓는 것보다 훨씬 안정적인 기준이었다.

ChatGPT CSS가 아니라 MyGPT 정책

처음에는 assistant 본문만 14px로 낮췄다. 그런데 user message와 code block을 실제 화면에서 보니 영역마다 크기가 다시 달랐다. assistant code는 12px, user code는 16px이었고, 짧은 user message는 p나 li가 아닌 별도 wrapper의 직접 텍스트로 렌더링되어 16px으로 남는 경우도 있었다.

 

여기까지 오니 특정 ChatGPT CSS 값을 따라가며 하나씩 보정하는 방식이 오히려 더 복잡해졌다. 결국 방향을 바꿨다. ChatGPT가 현재 몇 px을 쓰는지를 따라가지 않고, MyGPT가 대화와 입력 영역의 font-size를 14px로 소유하기로 했다. 최종 정책은 단순하다.

  • assistant 일반 본문: 14px
  • user 일반 본문: 14px
  • assistant/user block code: 14px
  • composer: 14px
  • sidebar: 기존 14px 유지
  • heading: 기존 크기 유지
  • zoom: 1.0 유지

font-size만 고정하고 line-height, font-family, font-weight는 ChatGPT의 값을 그대로 사용했다. 전체 문서에 * { font-size: 14px } 같은 광범위한 override를 걸지도 않았다.

Semantic selector만 사용

MyGPT가 ChatGPT 웹을 감싸는 래퍼인 이상 프론트엔드 변화의 영향을 완전히 피할 수는 없다. 그렇다고 현재 class 이름을 그대로 하드코딩하면 유지보수 비용은 더 커진다. 그래서 이번 구현에서는 실제 runtime에서 확인한 semantic attribute만 경계로 사용했다. style element는 고정 ID를 가진 하나만 삽입하고, 이미 존재하면 다시 추가하지 않는다. 성공한 page load 이후 한 번 적용하며 polling이나 MutationObserver도 사용하지 않는다.

 

WebEngine reset으로 새 Page가 생성되면 새 document에도 같은 정책을 다시 적용한다. 기존에 만든 renderer reset 구조와도 충돌하지 않도록 했다.

MyGPT 0.0.6

이번 변경은 MyGPT 0.0.6으로 정리했다. assistant와 user 본문, block code, composer를 모두 14px로 맞췄고, 짧은 사용자 메시지도 같은 정책이 적용되도록 보정했다. MyGPT는 ChatGPT를 새로 구현하려는 앱이 아니다. 한 PC에서 개인 계정과 회사 계정을 분리해서 사용하기 위해 별도의 QtWebEngine profile을 가진 전용 래퍼로 만든 앱이다. 개인 계정은 사적인 대화에, 회사 계정은 업무에 사용하려다 보니 데스크톱 앱 하나로는 세션을 나누기 어려웠고, 그 궁여지책으로 시작했다.

 

문제는 웹 서비스를 감싼 순간부터 이런 유지보수 비용이 생긴다는 점이다. CSS token 값 정도가 바뀌는 것은 이제 MyGPT의 14px 정책으로 막을 수 있지만, data-markdown-text-style, data-user-message-bubble, data-composer-markdown 같은 semantic DOM 구조 자체가 바뀌면 다시 확인해야 한다. 이번에는 단순히 폰트가 커진 문제였지만, 결국 QtWebEngine의 기본값과 페이지 CSS의 경계, computed style, DOM 구조까지 다시 따라가게 됐다. 래퍼 앱은 생각보다 신경 쓸 게 많다.

 

그래도 다음에 화면이 또 이상해지면 어디부터 확인해야 할지는 이제 정해졌다.