jeong-sik

tui-chat-design

"masc TUI 채팅 화면이 무엇을 어떤 기호로 그리는지 정리한 표와 규칙이다. 말한 사람 마크, 턴 레일, 왼쪽 여백의 순서, 시계 칸, Ctrl-F 밀도 단계, 밝기 위계와 끝난 일의 접힘, 줄 문구를 바꾸거나 새 기호를 넣기 전에 쓴다. 바꾼 화면을 실제로 띄워 확인하는 방법은 tui-pty-scenario 에 있다."

jeong-sik 4 Actualitzat fa 4 setm.
GitHub

Instal·la

npx skillscat add jeong-sik/masc/tui-chat-design

Instal·la des del registre de SkillsCat.

SKILL.md

masc 채팅 화면의 기호와 규칙

이 화면에는 이미 정해진 기호 체계가 있다. 새 기호를 만들기 전에 있는 기호로 말할 수 있는지 먼저 본다.
아래 값의 출처는 bin/masc_tui_message_layout.ml 과 bin/masc_tui_render_chat.ml 이다.
이 문서와 코드가 다르면 코드가 맞다. 그 자리에서 이 문서를 고친다.

말한 사람 — speaker_mark

기호 스타일 뜻
▶ User 운영자가 보냈다
◀ Inbound 바깥의 누군가가 여기로 보냈다
● Keeper 키퍼가 말한다
○ Status 그 턴에 대한 화면의 메모. 키퍼가 한 말이 아니다
› Local 입력줄 프롬프트와 같은 기호. 방금 친 것에 화면이 답한다
◈ Journal 나란히 도는 Memory 저널 레인
■ Tool 도구
◇ ◆ △ ✗ Skill _ 스킬: 도는 중 / 썼음 / 볼 것 / 실패
· Thinking 생각 중
✗ Error 오류

같은 ✗ 는 Overview 의 TUI Session Events 패널에서도 오류를 뜻한다. event_type = "error" 인 행만
시계 뒤에 ✗ 를 달고, 나머지 레벨은 표식 없이 그린다(Masc_tui_types.overview_event_mark).

이 표를 만든 규칙 둘

  • 기호는 도형이다. ? 같은 문장 부호는 쓰지 않는다. 끝난 줄에 물음표가 남으면 아무도 답하지
    않은 것처럼 읽힌다. 화면의 메모가 ○ 인 이유다.
  • 채운 것과 빈 것이 짝이다. ●/○, ◆/◇ 에서 채운 쪽은 그 일 자체이고, 빈 쪽은 그 일에
    대한 화면의 메모다. 새 상태가 필요하면 이 짝으로 말할 수 있는지 먼저 본다.

색은 구분의 근거가 못 된다

NO_COLOR 가 켜지면 색이 사라진다. 그래서 누가 말했는지는 색이 아니라 마크가 답해야 한다.
색으로만 구분되는 상태를 만들면 색 없는 터미널에서는 그 상태가 안 보인다.

밝기는 위계다

모양이 무엇인가를, 색이 누구인가·어떤 상태인가를 말한다면, 밝기는 위계만 말한다.
밝은 자리는 화면에 몇 개 없어야 한다.

  • 말만 전경이다. User·Inbound·Keeper 본문은 터미널 전경을 유지한다. Local 은
    명령에 대한 답이라 읽는 글이라 누르지 않는다.
  • 일·소식·생각은 한 단계 아래다. Tool·Skill·Journal·Thinking 본문은 dim 이다
    (Chat_theme.body, bin/masc_tui_ansi.ml). dim 본문 안의 bold·링크가 닫힐 때 dim 을 다시
    연다 — 스팬 하나가 그 뒤의 줄을 전부 밝히지 못한다.
  • 크롬은 가장 조용하다. 날짜 레일, 시계, 곁줄, 여백 라벨, 브레드크럼은 Theme.recede ()
    단계다. 스크롤 방향타로는 남지만 밝은 자리를 쓰지 않는다.
  • 실패는 이 규칙의 예외다. ✗ 와 failed 절은 reset→bold→bright red 로 서고, 실패 줄은
    접히지 않는다. 성공이 가라앉는 만큼 실패가 도드라져야 한다.

턴 레일 — turn_rail_glyph

레일은 "이 줄이 어느 턴에 속하나" 한 가지에만 답한다. 누가 말했나는 마크가 답한다.
한 기호에 두 질문을 싣지 않는다.

기호 값 뜻
╭ Rail_opens 턴이 이 줄에서 시작한다
│ Rail_says 턴 안의 말(운영자·키퍼·상태 등)
├ Rail_does 턴 안의 작업(도구·스킬·생각)
╶ Rail_stands 한 줄짜리 턴인데 그 줄이 작업이다
╰ Rail_closes 턴이 이 줄에서 끝났다
┤ Rail_joins 어느 턴에도 속하지 않은 줄이 선에 닿는다
빈칸 Rail_none 한 줄짜리 말, 또는 턴 밖의 줄
  • ┤ 앞에는 세 칸짜리 곁줄이 붙는다. 저널은 점선 ╌╌╌, 다른 사람이 보낸 줄은 실선 ─── 이다.
    종류는 기호가 아니라 선 모양으로 말한다. 마크와 라벨이 이미 누구인지 말하기 때문이다.
  • 괄호(╭…╰)는 붙어 있는 줄에만 친다. 턴 밖의 줄(저널 기록, 남의 broadcast)은 괄호를 끊지
    않고 지나간다. 다른 턴의 줄이 끼어들어 한 턴이 둘로 갈라지면, 괄호 대신 외톨이 줄 두 개로 그린다.
    한 칸짜리 세로줄로는 떨어진 줄을 한 턴이라고 말할 수 없다.
  • 한 줄짜리 말은 레일을 그리지 않는다. 대화의 거의 모든 줄에 레일이 붙으면 아무도 레일을 안 본다.

왼쪽 여백의 순서는 고정이다

한 줄은 왼쪽부터 이 순서다. 순서를 바꾸면 세로줄이 어긋난다.

  1. 레일 여백 — turn_rail_cells = 5. 곁줄 3칸 + 기호 1칸 + 띄움 1칸.
    곁줄이 없는 줄도 5칸을 다 쓴다. 폭이 줄마다 달라지면 스크롤할 때 본문 줄바꿈이 바뀐다.
  2. 시계 — chat_clock_column = 5 칸 + 띄움 1칸. Origin_inline 에서만 그린다.
    모든 줄의 시계 칸은 같은 5칸이다. 칸 폭이 줄마다 다르면 본문 줄바꿈도 줄마다 달라진다.
    시계는 recede 단계로 눌려 있고, 마크만 말한 사람의 색을 지킨다
    (시계 칸의 경계는 gutter_clock_cells).
  3. 마크 + 띄움 + 라벨 — 라벨 칸은 chat_role_label_column = 10. 마크 칸은 라벨 칸 안에서 뺀다.
    이름이 넘치면 가운데를 줄인다(fit_middle). 마크는 줄이는 범위 밖에 있어서 긴 이름에서도 남는다.
    도구·스킬·생각 줄은 라벨 단어를 쓰지 않는다 — 마크가 이미 레인을 말한다.
    칸은 그대로 두고 마크와 띄움만 그린다.
  4. 인용 막대 — 남이 쓴 글(Tool·Skill·Status·Local)은 흐린 │, Journal 은 ┊,
    말(User·Inbound·Keeper·Error·Thinking)은 빈칸 두 칸이다.
  5. 본문

폭이 모자라면 레일을 먼저 버린다. 그래도 이름이 안 들어가면 시계와 마크를 버리고 이름을 줄여 남긴다.
누가 말했는지가 언제 말했는지보다 중요하다.

같은 값을 반복해서 그리지 않는다

  • 시계는 분이 바뀐 줄에만 그린다. 같은 분에 여섯 줄이 오면 한 줄만 시각을 달고 나머지는 빈칸이다.
    칸은 그대로 비워 둬서 아무것도 밀리지 않는다.
  • 같은 사람이 이어서 말하면 이름을 지운다. 마크도 가장 조용한 기호로 바뀐다
    (continued_mark: User·Inbound·Keeper·Tool 은 │, Thinking 은 ·, 나머지는 원래 마크).

밀도는 세 단계이고 Ctrl-F 로 돈다

단계 모양
Origin_inline (기본) 출처를 여백에 접고, 분이 바뀐 줄에 짧은 시계를 단다
Origin_row 헤더 한 줄: 이름 전체, 오른쪽 끝 recede 시계, 사이 rule
Origin_bare 시계를 떼고 마크와 이름만 남긴다

Origin_row 헤더의 시각 칸이 비면(timeline_bucket = None) 시계를 안 그리고 --:--:--
자리표도 쓰지 않는다. 같은 화자가 이어지는 행은 이름 없이 rule 과 시계만 그린다
(metadata_row, bin/masc_tui_render_chat.ml 의 origin_heading).

Ctrl-F 는 Origin_inline → Origin_row → Origin_bare → Origin_inline 으로 돈다
(Masc_tui_types.next_origin_display). 기본값은 Masc_tui_types 의 상태 생성에서 정한다.

기본값 하나만으로 읽을 수 있어야 한다. "Ctrl-F 누르면 보인다" 는 기본값이 모자란다는 뜻이다.

끝난 일은 접힌다

끝난 일은 화면의 주인공이 아니다. 무엇이 남고 무엇이 접히는지는 정해져 있다.

  • 끝난 스킬은 요약 한 줄이다. 전달됨, 도구 씀 · 이름 · N action
    (Keeper_chat_transcript.skill_rows). 전달만 됐는지 실제로 쓰였는지는 기본 밀도에서
    읽혀야 한다 — 이름만 남기는 접힘은 금지다.
  • ↳ 호출 줄, proof · turn=… · use=…, detail 줄은 Ctrl-D 뒤다.
    msg_tool_visibility = Tools_full 일 때만 그린다. 새 키를 만들지 않고 기존 도구 펼침 축에
    탄다.
  • 도구 블록은 기본 Tools_compact 다. 반환된 호출은 inventory 헤더 한 줄로 접히고,
    실패·미반환·진행 중 호출은 헤더의 절이 아니라 자기 마크의 독립 줄로 남는다 — 단 셋
    이상 모였을 때만. 둘 이하면 쪼개도 Full 이 그리는 두 줄이라 접지 않고, 실패도 헤더가
    1 failed: web_fetch 처럼 이름을 불러 한 줄이다 (project_tool_block Compact 팔,
    bin/masc_tui_keeper_chat_transcript.ml).
  • 돌아가는 동안에도 같은 토글로 그린다. 라이브 블록과 확정된 블록은 같은
    project_tool_block / skill_rows 를 지난다. 줄이 접히는 시점은 완료가 아니라
    Ctrl-D 토글이다.

줄의 문구

  • 게이트 줄은 가운뎃점으로 소식과 대상을 잇는다. 앞이 소식, 뒤가 대상이다
    (bin/masc_tui_gate_text.ml: 승인됨 · 적용 예정, 판정 중 · 이 호출은 미뤄짐).
  • 가운뎃점은 서로 다른 칸을 가르는 데만 쓴다. 한 상태에 사실이 둘이면 쉼표로 잇는다
    (bin/masc_tui_keeper_chat_transcript.ml: 읽음, 전달 확인 중, 전달됨, 도구 안 씀).
    읽음 · 전달 기록 없음 이라고 쓰면 상태 하나인지 둘인지 읽는 사람이 알 수 없다.
  • 상태 이름은 모델이 무엇을 했는지로 쓴다(읽음, 전달됨, 도구 씀). 서버가 보내고 받은 동사로
    쓰면 누가 누구에게 보냈는지 운영자가 알 수 없다.
  • 한국어는 한국 사람이 실제로 쓰는 말로 쓴다. 영어 라벨을 품사째 옮기지 않는다.
  • 한글 문구는 소스에 리터럴로 쓴다. 바이트 이스케이프(\xeb\x83\xa8)로 손으로 적지 않는다.
    손으로 맞추다 틀리기 쉽다.

고치고 나면

  • 화면은 코드를 읽어서 판정하지 않는다. tui-pty-scenario 스킬로 실제로 띄워 본다.
  • 문구·기호·폭을 바꿨으면 test/*.py 가 기다리는 글자열(needle)에서 옛 문구와 옛 기호의 바이트열을
    찾아 같이 고친다. 렌더 파일만 고친 PR 은 PR CI 가 키보드 산책을 돌리지 않는다.
  • 하네스가 못 보는 것은 사람에게 묻는다: 색, 사용자 글꼴이 ╶ ╌ ┊ ◈ ○ 를 그리는지, 실제 폭에서 읽히는지.