본문 바로가기
C.W.K.
Stream
Lesson 03 of 05 · published

역할 둘 — voice underline

~12 min · voice-underline, voice-doctrine, mark-decoration, job-two

Level 0식은 초고
0 XP0/33 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
"맞춤법 검사기는 사전을 알고, voice 검사기는 너를 알아. Rekindle 의 두 번째 decoration 역할이 뒤쪽이야."

역할 둘 — 맞춤법 검사기가 아니라 voice 검사기

decoration 엔진의 두 번째 역할은 세상 어디에도 없는 물건이야. 라이브 voice 검사기. 글을 쓰는 동안 voice 규칙에 걸리는 구절에 밑줄을 긋고 왜 걸렸는지 설명하는 작은 여백 노트를 띄워. register 가 미끄러졌거나, token-mirror 함정에 빠졌거나, 단어가 지나치게 셀 때 같은 경우지. 맞춤법 검사기는 네 텍스트를 모두가 공유하는 사전에 맞춰 보지만, voice 검사기는 독트린에 맞춰 봐. 한 작가가 중요하다고 정한 함정과 선호 말이야. 밑줄을 제자리에 긋는 UX 는 맞춤법 검사와 같은데, 겨누는 대상이 지극히 사적인 거지.

독트린의 실제 예를 하나 들면, 문서로 정리된 한국어 규칙 중에 박다 를 잡아내는 게 있어. 더 부드러운 표현이 맞는 자리에 지나치게 센 동사가 들어갔을 때 표시하는 거지. 문법 오류가 아니라서 어떤 범용 도구도 절대 못 잡아. 이건 voice 규칙이고, 한 작가의 독트린을 중심으로 지은 voice 검사기만 지켜낼 수 있어.

탐지는 경계 하나로 묶는다

메커니즘으로 보면 mark 에 widget 을 더한 거야. 문제가 된 범위에 밑줄을 긋고 (mark) 그 옆에 노트를 붙이면 (widget) 끝이지. 지능은 전부 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 가 두 번째로 확인되는 지점이고, 세상 어디에도 없는 voice 검사기라는 진짜 새 기능이 새 기계장치를 거의 안 들이고 지어진 이유이기도 해.

Code

탐지는 경계 하나 뒤에 두고, decoration 은 범위만 받아 써·typescript
type VoiceIssue = { from: number; to: number; note: string };

// 뭐가 voice 이슈인지 아는 건 여기 하나뿐이야.
// 지금은 결정론적 규칙이고, 나중엔 Rust 엔진이나 AI 판단 레이어가 될 수도.
function detectVoiceIssues(doc: string): VoiceIssue[] {
  // 예: 더 부드러운 표현이 맞는 자리에 지나치게 센 동사가 오면 표시
  return scanVoiceRules(doc);
}

// decoration 빌더는 탐지가 어떻게 바뀌든 그대로야.
function buildVoiceDecorations(state) {
  return detectVoiceIssues(state.doc.toString()).flatMap((issue) => [
    Decoration.mark({ class: "cm-voice-issue" }).range(issue.from, issue.to),
    Decoration.widget({ widget: new MarginNote(issue.note), side: 1 }).range(issue.to),
  ]);
}

External links

Exercise

네 글쓰기에 쓸 voice 규칙을 하나 만들어봐. 네가 남용하는 단어라든가, 자꾸 미끄러져 들어오는 register, 잡아내고 싶은 표현 같은 것. 그다음 그게 decoration 으로 어떻게 드러날지 설명해봐. 어느 범위에 밑줄이 가고 여백 노트에는 뭐라고 쓸 거야? 방금 Live Preview 와 같은 메커니즘으로, 어떤 범용 도구도 안 싣는 기능 하나를 설계했다는 걸 확인해.
Hint
규칙을 범위와 노트를 돌려주는 함수 모양으로 잡아봐. 문서 텍스트가 들어오면 이슈가 어디부터 어디까지고 한 줄짜리 이유는 뭐야? 그게 네 voice 의 detectVoiceIssues 야. decoration 코드는 이미 다 쓰여 있고.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.