"맞춤법 검사기는 사전을 알고, voice 검사기는 너를 알아. Rekindle 의 두 번째 decoration 역할이 뒤쪽이야."
역할 둘 — 맞춤법 검사기가 아니라 voice 검사기
decoration 엔진의 두 번째 역할은 범용 에디터가 기본으로는 절대 안 싣는 물건이야. 라이브 voice 검사기. 글을 쓰는 동안 voice 규칙에 걸리는 구절에 밑줄을 긋고 왜 걸렸는지 설명하는 작은 여백 노트를 띄워. register 가 미끄러졌거나, token-mirror 함정에 빠졌거나, 단어가 지나치게 셀 때 같은 경우지. 맞춤법 검사기는 네 텍스트를 모두가 공유하는 사전에 맞춰 보지만, voice 검사기는 네 독트린에 맞춰 봐. 한 작가가 중요하다고 정한 함정과 선호 말이야. 밑줄을 제자리에 긋는 UX 는 맞춤법 검사와 같은데, 겨누는 대상이 지극히 사적인 거지.
독트린의 실제 예를 하나 들면, 문서로 정리된 한국어 규칙 중에 박다 포화를 보는 게 있어. 한 문단에 박다 계열이 두 번 이상 들어가면 표시하는 거야. 문법 오류가 아니라서 범용 도구엔 이런 규칙이 안 들어 있어. 이건 voice 규칙이야. 설정할 수 있는 prose linter(Vale 가 잘 알려진 예야)라면 이런 규칙을 가르칠 수는 있어. 엔진이 귀했던 적은 없거든. 어떤 도구도 안 싣는 건 doctrine 이야. 한 작가가 몇 년의 실제 교정에서 적어낸 규칙들, 그리고 그 교정과 계속 발맞추는 일. voice 검사기는 그 doctrine 을 글이 실제로 쓰이는 자리에 배선한 거야. (라이브 배선은 옵트인이야. 전체 voice 세트는 pippa voice 프로필에서 돌고, 기본 prose 패스는 이런 좁은 규칙만 골라서 돌려.)
탐지는 경계 하나로 묶는다
메커니즘으로 보면 mark 에 호버 노트야. 문제가 된 범위에 밑줄을 긋고 (mark), 그 위에 마우스를 올리면 설명이 툴팁으로 떠 — 위젯을 끼워 넣는 게 아니라. 지능은 전부 detectVoiceIssues(doc) 라는 함수 하나 뒤에 숨어 있어. 이 함수는 문자 범위와 노트를 돌려주기만 해. 지금은 TypeScript 로 짠 결정론적 규칙이 탐지를 맡지만, 나중에 Rust voice 엔진으로 옮기거나 AI 판단 레이어를 키워도 decoration 코드는 그대로야. 범위만 받아 쓰니까. 요령은 전부 이 경계에 있어. '뭐가 문제인가' 를 함수 하나 뒤에 가둬두면 '어떻게 그릴 것인가' 는 탐지가 어떻게 진화하든 그대로 남거든.
type VoiceIssue = { from: number; to: number; note: string };
// 탐지는 경계 하나로 묶여 있어. 지금은 규칙이고 나중엔 Rust 나 AI 판단일 수도.
// decoration 레이어는 그대로야. 범위만 받아 쓰니까.
function detectVoiceIssues(doc: string): VoiceIssue[] { /* ... */ }
같은 엔진, 새 목적
한 발 물러나서 보면, 이건 Live Preview 의 기계장치를 그대로 두고 겨누는 질문만 바꾼 거야. Live Preview 는 '마크업이 어디 있지' 를 묻고 mark 를 그렸어. voice underline 은 'voice 이슈가 어디 있지' 를 묻고 mark 에 노트를 더해 그리고. 원시 요소는 같고 입력만 새로운 거지. 이 트랙의 thesis 가 두 번째로 확인되는 지점이야. 범용 에디터는 안 싣는 기능, 그러니까 한 작가의 doctrine 을 검사하는 기능인데도, 그리는 쪽에선 따로 새 기계장치가 필요 없었던 이유가 여기 있어. 공을 들인 건 밑줄 긋기가 아니라 doctrine 과 탐지 쪽이었지.