일자: 2026-07-14 선행: 060번 (파이프라인 v2.0 "구현 완료" 선언, 단 "publisher.py 실제 테스트 실행"은 ⏸ 미완으로 남김) 이번 세션: 그 미완 항목을 실제로 돌려봄 → 전부 깨져있었음을 raw 증거로 확인 → 근본원인 5건 수정 → 가독성 자동검증 도구 신설
060번 개발일지 이후 실제로 publisher.py/_tistory_publish.py로 발행을 시도한 첫 실사용 세션.
https://programmer-park.tistory.com/71~74 4개 포스트가 이미 "발행 완료" 로그를 남긴 상태였음.
raw 증거 — RSS로 직접 확인:
$ curl -s https://programmer-park.tistory.com/rss
...
<item><title>IT OJT 매뉴얼 003</title><link>.../71</link>...
<item><title>Tistory 네이티브 HTML 테스트</title><link>.../72</link>...
<item><title>인라인 스타일 테스트</title><link>.../73</link>...
<item><title>IT OJT 매뉴얼 003</title><link>.../74</link>...
raw 증거 — 실제 HTML 본문:
$ curl -s https://programmer-park.tistory.com/74 | grep tt_article_useless
<div class="tt_article_useless_p_margin contents_style"><p data-ke-size="size16"></p></div>
4개 전부 동일 — 본문이 완전히 비어있었다.
tinymce.setContent()만으로는 발행 시 반영 안 됨_tistory_publish.py의 본문 주입 로직:
tinymce.activeEditor.setContent(html); // 이것만으로는 부족
setContent는 에디터 화면(iframe)엔 반영되지만, 티스토리의 React 상태(발행 버튼이 실제로 읽는 값)로는 동기화 안 됨.
수정:
ed.setContent(html);
try { ed.fire('change'); } catch(e2) {}
try { ed.fire('input'); } catch(e2) {}
try { ed.save(); } catch(e2) {}
getContent().length 검증 단계 추가 (100자 미만이면 fail 처리).검증: 수정 후 재발행 → curl 실측 본문 길이 17,000+자 확인.
generator.py의 build_booklet()이 커버에 <h1>{title}</h1>을 항상 찍는데,
초안 마크다운 첫 줄도 # 제목으로 시작해서 같은 제목이 두 번 나옴.
수정:
content_md = re.sub(r'^#\s+.+\n+', '', content_md, count=1)
:::alert-blue 제목처럼 같은 줄에 제목 붙으면 통째로 미변환정규식 r':::alert-(\w+)\n(.*?):::'은 타입 바로 뒤에 \n이 와야 매칭되는데,
초안에 :::alert-blue 💎 요약\n처럼 타입 뒤에 제목 텍스트가 붙어있어서 매칭 자체가 실패.
1차 수정 시도((?:[ \t]+(.+))?)도 DOTALL 하의 그리디 .+가 줄바꿈까지 삼켜서
제목 그룹이 본문까지 통째로 먹어버리는 2차 버그 발생 — [^\n]+로 줄 안으로 제한해서 해결.
:::callout 도 동일 패턴의 버그, 동일 방식으로 수정.
--- 구분선이 <hr>로 안 바뀌어 화면에 텍스트로 노출md_to_html()에 수평선 변환 규칙 자체가 없었음. 추가:
html = re.sub(r'^-{3,}$', '<hr>', html, flags=re.MULTILINE)
로컬 파일 기준 가독성 검증은 통과했는데, 실제 라이브 페이지에서는 h2/h3가 거의 안 보였다. 박씨가 직접 "안 보여"라고 지적. Playwright headless 스크린샷 + 크롭으로 raw 확인:
2장: 필수 도구 스택, 생존 전략 등 h2/h3 텍스트가 검정에 가까운 색으로 렌더링됨 (다크그린 배경 위에서 거의 안 보임)근본원인 조사 — CDP CSS.getMatchedStylesForNode로 실제 승리 규칙 확인:
regular | .article_view h1, .article_view h2, .article_view h3, .article_view h4 | color: rgba(0,0,0,.87)
regular | .book-root h1...h6 | color:var(--text-bright) ← 내 규칙
regular | #article-view h2, #article-view h3, #article-view h4 | color: #000 ← 실제 승자
티스토리 스킨이 ID 선택자(#article-view)로 h2/h3/h4 색을 강제하고 있어서,
클래스 선택자인 내 규칙은 명시도 규칙상 절대 이길 수 없었다. li(.article_view ul li),
링크(#article-view a), blockquote도 동일 패턴으로 확인됨.
수정: booklet.css + generator.py의 BOOKLET_CSS 양쪽에 색상/배경 선언 81건에
일괄 !important 적용 (정규식 스크립트로 자동 처리, 수작업 아님).
검증: 수정 후 라이브 재검증 — 첫 시도는 여전히 .btn-purple 대비 1.74:1 실패
→ CSS에 .btn-purple 클래스 자체가 없었던 걸 발견 (gold/blue/red 변형도 함께 없었음) → 4종 추가 →
재검증 통과.
publishing/verify_readability.py — Playwright 기반, 실제 렌더링을 열어:
.book-root 내부 텍스트 보유 요소 전수 순회, 조상 체인 알파 합성으로 실제 배경색 계산 →
WCAG AA(4.5:1) 대비 미달 검출. (반투명 오버레이를 배경 자체로 오인하는 오탐 방지 로직 포함)details.acc(아코디언) 전수 클릭 → open 속성이 실제로 토글되는지 인터랙션 검증사용: python3 verify_readability.py <url 또는 file://경로>
실패 시 exit 1 + 문제 목록. --publish 뒤에는 항상 이걸 통과해야 "완료"로 보고한다.
로컬 파일 검증 → 라이브 URL 재검증까지 이중으로 통과한 뒤에야 "완료"로 처리하는 걸 이번 세션 이후 규칙으로 확정.
71~74번(전부 버그 #1로 인해 빈 글이었던 것들) 비공개 전환 완료.
programmer-park.tistory.com/75 하나만 공개 상태로 최종 확정.
참고: Tistory manage/posts 페이지의 행별 옵션 버튼은 CSS :hover로만 노출되는 0×0 크기 요소라
Playwright 일반 클릭은 전부 타임아웃남 — .hover() 선행 후 force:true 클릭으로 해결.
| 파일 | 변경 |
|---|---|
publishing/generator.py |
제목중복 제거, alert/callout 정규식 수정, <hr> 변환 추가, btn 색상 4종 추가, 전체 색상 규칙 !important |
publishing/template/booklet.css |
동일 내용 미러링 |
publishing/verify_readability.py |
🟢 신규 — 가독성/인터랙션 자동 검증기 |
dtslib-papyrus/tools/mcp_distributor/_tistory_publish.py |
setContent 후 change/input 이벤트 + save() + getContent 검증 추가 |
parksy_distribute_tistory MCP 도구 자체의 asyncio.run() 중첩 에러(서버가 이미 이벤트루프 안이라 발생) — 이번엔 spec 파일만 재사용해서 별도 프로세스로 우회했음. MCP 서버 코드 자체 수정은 미완._tistory_console.py의 list_posts() 셀렉터도 깨져있음("통계"만 반환) — 우선순위 낮음, RSS로 대체 가능해서 급하지 않음.publisher.py(parksy-logs 자체 버전, TinyMCE 감지 실패로 애초에 안 쓴 것)는 여전히 미수정 — _tistory_publish.py가 사실상 대체재로 검증됨."발행 로그가 있다고 발행된 게 아니다. 화면을 직접 열어봐야 발행된 거다."