# Concepts

> 채널, 옵션, 정규화 보고, 스트리밍 계약. mdwire 의 모든 API 가 함께 쓰는 규칙입니다.

HTML: https://minjun.kim/mdwire/ko/docs/

Rust 코어, npm 패키지, Go 구현, CLI 는 같은 입력을 받아 같은 결과를 출력합니다. 네 구현 모두 같은
[테스트 케이스](https://github.com/minjun0219/mdwire/tree/main/corpus/cases)를 통과해야 합니다. 이 페이지는 네
구현이 함께 쓰는 규칙을 다룹니다. 정확한 이름은 언어별 페이지에 있습니다.

이 문서는 `main` 브랜치 기준입니다. (다음 릴리스) 가 붙은 기능은 지금 npm, crates.io, Go 에 배포된 버전에는
아직 없고, 다음 릴리스에 들어갑니다.

## 채널

출력을 보낼 곳입니다. 이름은 어느 구현에서나 같은 문자열입니다.

| 이름 | 보내는 곳 | 길이 제한 |
|---|---|---|
| `telegram-html` | 텔레그램 `parse_mode: "HTML"` 입니다. 태그 아홉 개(`b i u s code pre a blockquote tg-spoiler`)만 받습니다. 헤딩과 표가 없어서 표는 고정폭 블록으로 출력합니다. | 4,096 |
| `slack-markdown` | 슬랙 `markdown_text` 입니다. 표준 마크다운을 슬랙이 직접 변환하므로 mdwire 는 정규화와 분할을 하고, 문자로 쓴 `*` 는 `\*` 로 출력합니다. | 12,000 |
| `github-markdown` | GitHub 코멘트와 PR 본문(GFM)입니다. 문자로 쓴 `~` `<` 는 이스케이프합니다. GFM 이 닫지 않는 굵게(한국어 조사 앞)는 `<strong>` 으로 출력합니다. | 65,536 |
| `notion-markdown` | 노션 페이지 본문(Notion-flavored Markdown)입니다. 헤딩은 네 단계까지 씁니다. 노션이 렌더링하지 못하는 인라인 HTML 은 제거하고 오토링크는 `[url](url)` 로 씁니다. | 65,536 (측정 안 함) |
| `plain` | 평문입니다. 마커를 모두 빼므로 폴백용으로 씁니다. | 12,000 |
| `html` | 브라우저에 넣을 HTML 조각입니다. `innerHTML` 로 바로 넣어도 안전합니다. 텍스트는 이스케이프하고 원문 인라인 태그의 속성은 버립니다. 링크는 허용한 스킴에만 만듭니다. | 없음 |

길이 제한은 렌더링한 출력의 글자 수입니다. 보내는 쪽에서 따로 정할 수도 있습니다([옵션](#옵션)).
예를 들어 `plain` 을 텔레그램에 보낸다면 4,096 에 맞춰야 합니다.

## 옵션

| 옵션 | 기본값 | 설명 |
|---|---|---|
| limit | 채널의 길이 제한 | 조각 하나의 글자 수입니다. 256 보다 작으면 256 으로 올립니다. 조각마다 마크업을 닫고 다시 열 공간이 필요하기 때문입니다. 스트리밍과 `html` 채널은 나누지 않습니다. |
| html · 줄바꿈 | `br` | 블록 안의 줄바꿈을 `<br>` 로 출력합니다. `space` 를 주면 공백으로 바꿔 브라우저가 이어 붙이게 합니다(한 줄을 80자로 접어 쓴 글에 알맞습니다). |
| html · 이미지 | `link` | `![alt](url)` 을 링크로 출력합니다. 클릭하기 전에는 아무것도 불러오지 않습니다. `load` 를 주면 `<img>` 로 불러옵니다(허용한 스킴만). |
| html · 스킴 | `http`, `https`, `mailto` | 링크와 이미지 주소로 허용할 스킴입니다. 목록을 주면 기본값에 더하지 않고 통째로 바꿉니다. |

`html` 옵션 세 가지는 `html` 채널에서만 쓰입니다.

## 정규화 보고

mdwire 는 렌더링하면서 한 일도 셉니다. 앞의 다섯 가지는 **고친 것**으로, 모델이 자기 서식을 얼마나 자주
깨는지 보여 줍니다.

| 필드 | 세는 것 |
|---|---|
| 닫아 준 강조 | 블록이 끝날 때까지 닫히지 않아서 닫아 준 강조(`**영향 범위`)를 셉니다. |
| 닫아 준 펜스 | 문서 끝까지 닫히지 않아서 닫아 준 코드펜스를 셉니다. |
| 되돌린 코드 스팬 | 짝이 없어서 문자로 되돌린 연속된 백틱을 셉니다. |
| 버린 마커 | 짝이 없어서 버린 `**` 를 셉니다. `꼬리**` 처럼 앞에 글자가 붙은 마커가 해당합니다. |
| 추측으로 짝지은 강조 | 규칙이 아니라 추측으로 짝지은 강조를 셉니다. CommonMark 가 열지 않는 여는 마커(`값**(합계)**를`)를 같은 줄의 거울 모양 닫는 마커와 짝지은 횟수입니다. 양 끝이 ASCII 영숫자인 수식(`x**(y)**z`)은 세지 않습니다. 0 이 아니면 한 번 볼 만한 답변입니다. (다음 릴리스) |

뒤의 여섯 가지는 **채널에 맞춰 바꾼 것**입니다. 원문에는 문제가 없어도 채널이 그대로 받지 못해서
보내기 전에 바꿔 쓴 것을 셉니다.

| 필드 | 세는 것 |
|---|---|
| 이스케이프한 글자 | 채널이 구문으로 읽을 수 있어서 이스케이프한 글자를 셉니다(GitHub 의 `\~` `\<` `\*`, 슬랙·노션의 `\*`). |
| 태그로 출력한 강조 | 채널이 마커로 읽지 못하는 자리라서 다르게 출력한 강조를 셉니다. GitHub 은 `<strong>` 으로 출력하고 슬랙은 마커 안쪽에 보이지 않는 U+2060 을 넣습니다 (다음 릴리스). |
| 제거한 HTML | 제거한 원문 HTML 을 셉니다. 채널이 렌더링하지 못하는 태그, 주석, 줄바꿈으로 바꾼 `<br>` 이 해당합니다. |
| 바꿔 쓴 불릿 | 채널 표기로 바꾼 목록 기호를 셉니다(`* ` `• ` → `- `, 텔레그램은 `- ` → `• `, `1)` → `1.`). |
| 다시 쓴 표 | 구분선과 칸 공백을 정리하거나 고정폭 블록으로 바꾼 표를 셉니다. |
| 바꿔 쓴 마커 | 다른 표기로 바꿔 쓴 강조와 링크(`_기울임_` → `*기울임*`, `__굵게__` → `**굵게**`, `<url\|텍스트>` → `[텍스트](url)`)를 셉니다. 마크다운을 출력하는 채널에만 해당합니다. |

## 스트리밍 계약

스트리머는 모델 출력을 토큰 단위로 받아 지금 보내도 안전한 만큼을 반환합니다.

- **`push` 가 반환한 출력은 확정입니다.** 나중에 들어온 입력이 이미 반환한 출력을 고치지 않고,
  `finish` 는 뒷부분만 덧붙입니다. 그래서 `push` 결과를 이어 붙이면 입력을 어떤 크기로 끊어 넣었든
  한 번에 `render` 한 결과와 같습니다. 단, 문서가 길어서 `render` 가 길이 제한에 맞춰 여러 조각으로
  나누는 경우는 예외입니다. 스트리밍은 나누지 않기 때문입니다.
- **보류하는 것은 꼭 필요한 만큼입니다.** 아직 무엇인지 판단할 수 없는 줄 머리, 청크 끝에 걸린 연속된
  마커, 아직 닫히지 않은 강조의 안쪽만 보류합니다. 문단 전체를 보류하지는 않습니다.
- **스트리밍은 길이 제한으로 나누지 않습니다.** 예외가 하나 있습니다. 길이 제한을 넘는 노션 표는
  머리글을 되풀이한 여러 표로 나눠 출력합니다. 스트리밍에서도 같습니다.

스트리밍 중에 보내는 방법은 채널에 따라 두 가지입니다.

| 채널이… | 보낼 것 | 예 |
|---|---|---|
| 메시지 전체를 다시 쓰는 경우 | 누적 출력 **+ `preview()`** 를 보냅니다. `preview()` 는 보류 중인 내용을 입력이 여기서 끝난 것처럼 렌더링한 뒷부분입니다. `finish` 다음에 `revised()` 가 거짓이면 마지막 편집은 건너뜁니다. | 텔레그램 `editMessageText`, 슬랙 `chat.update`, React |
| 덧붙이기만 하는 경우 | `push` 결과를 그대로 보냅니다. `preview()` 는 호출하지 않습니다. | 슬랙 `appendStream` |

`closeOpen()` 은 `preview()` 보다 엄격합니다. 이미 보낸 블록 마크업(`<blockquote>`, `<pre>`)만 닫고,
보류 중인 내용은 렌더링하지 않습니다. 두 메서드 모두 스트리머의 상태를 바꾸지 않습니다.
