3-8. 다시 켜도 이어지는 작업 (9/22 ~ 9/26)

앞 편에서 언어 서버를 붙였다면, 이번에는 작업을 끊었다가 돌아오는 자리를 메웠다. 저장하지 않은 글은 크래시를 넘어 돌아오고, 재부팅 뒤에는 원래 칸에서 셸과 에이전트 대화를 다시 연다. 편집기에는 인레이 힌트부터 괄호 색과 공백 표시까지 붙었다. 세션을 많이 열었을 때의 비용은 새 알고리즘보다 할 필요 없는 일을 찾는 것에서 줄었다.

이 편은 9월 26일 확인한 공개 main(6485c447)을 기준으로 썼다. 날짜는 한국 시간의 병합·커밋 시각이다. PR이 병합됐더라도 대상이 다른 개발 브랜치이면 이 편의 구현 범위에 넣지 않았다.

저장하기 전의 글도 작업이다 (9/22~9/23)

파일을 열고 저장하는 것과, 쓰던 글을 잃지 않는 것은 다른 문제였다. 저장 충돌에 비교를 붙이고, SSH 세션에서 만든 문서는 어느 기계에 쓸지 고르게 했다. 이어서 미저장 편집의 백업·복원, 이름 없는 문서의 복원까지 닫았다. 저장 충돌 비교 #3848, 원격 저장 #3853, 미저장 백업 #3858.

원격 저장은 SSH 세션에서 새로 만든 문서의 목적지를 고르는 기능이다. 기존 원격 파일을 열어 편집하는 경로는 아직 읽기 전용 미러이며, 원격 파일의 외부 변경 충돌 검사와 일괄 저장도 이번 범위에서 제외했다.

백업은 저장하거나 닫기를 수락하면 지운다. 크래시 뒤에는 편집 중이던 내용을 되살리고, 디스크와 충돌하는지는 저장하는 순간에 묻는다. 이름 없는 문서는 workspace에 본문을 집어넣는 대신 백업을 가리키는 번호를 남긴다. 파일의 신원을 잃은 경우에도 내용을 버리는 대신 이름 없는 문서로 복구한다. 이 흐름이 합쳐지며 이름 없는 문서 계획의 열 조각이 닫혔다. 복원 #3866, 이름 없는 문서 #3869, 신원 유실 #3871, 계획 마감 #3873.

보호 범위도 정해져 있다. 네이티브 편집기 문서를 대상으로 편집이 멎은 뒤 2초와 앱 종료 직전에 백업하며, 8MiB를 넘으면 백업을 멈추고 상태바에 알린다. 따라서 갑작스러운 크래시 직전의 아직 백업되지 않은 입력까지 보장하지는 않는다. CM6 웹 편집 경로와 이전 undo 스택은 복원 범위 밖이다. 백업 계약 §3.10.

에이전트 탭의 턴 목록도 앱을 다시 열면 남는다. 대화 재개와는 별개로, 어느 턴이 어떤 파일을 만졌는지 돌아볼 이력을 디스크에 남기는 작업이다. 저장 대상은 봉인된 턴이고 보관 기간은 7일이다. 진행 중인 턴과 목록의 선택·펼침 상태까지 이어지는 것은 아니다. 턴 목록 영속화 #3852.

재부팅은 프로세스의 복원이 아니다 (9/23~9/24)

앱 종료를 견디는 세션 호스트도 컴퓨터 재부팅을 견디지는 못한다. 그래서 복원 규칙에 재부팅을 확인하는 조건이 생겼다. 이전 부팅의 세션임이 확인되면, 죽은 세션을 표시하는 대신 저장된 작업 디렉터리에서 새 셸을 띄운다. 그 칸에 로컬 에이전트의 세션 ID가 남아 있으면 대화 파일을 찾아 같은 칸에서 이어간다. 새 셸 복원 #3891, 에이전트 재개 #3894.

권한 모드와 모델은 대화 파일에서 읽는다. 대화 파일이 없으면 일반 셸로 돌아가고, 이어가지 못한 수를 알린다. keep-alive를 끈 칸에도 같은 규칙을 적용했고, 두 번 재개되는 경우와 신원 수명·실행 실패를 후속 리뷰에서 고쳤다. keep-alive 보정 #3902, 복원 리뷰 #3904.

여기서 복원한 것은 작업 자리와 대화다. 원격 SSH 칸의 에이전트 재개와 스크롤백 복원은 이 범위에 없다. 실제 앱에서 재개 명령이 만들어지는 경로는 확인했지만, 실제 컴퓨터 재부팅과 실제 계정의 대화를 끝까지 잇는 E2E는 해당 PR의 미검증 항목으로 남아 있다.

편집기에서 매일 보는 것들 (9/22~9/26)

앞 편의 LSP 기능 뒤로 네 가지가 이어졌다. 인레이 힌트는 타입 같은 가상 텍스트를 줄 안에 끼우고, 문서 심볼은 상단 심볼 체인과 피커를 채운다. 같은 심볼의 다른 위치를 강조하고, 선택은 낱말에서 구문 구조·줄·문서로 넓히고 다시 좁힐 수 있다. 인레이 힌트 #3859, 문서 심볼 #3872, 심볼 강조 #3876, 선택 확장 #3895.

병합일화면에서 달라진 것
9/24스크롤해도 현재 스코프의 머리줄이 위에 남는 sticky scroll, 현재 줄·짝 괄호 강조
9/25파일에서 간격을 추정하는 들여쓰기 안내선, 중첩 괄호의 색 구분, 공백·탭 표시
9/26커서가 괄호에 닿지 않아도 감싸는 쌍이나 다음 여는 괄호로 이동

sticky scroll은 최대 다섯 줄, 본문 높이의 25% 안에서 커서를 가리지 않게 한다. 공백 표시는 기본적으로 선택 안의 공백을 ·, 탭을 →로 보여 준다. 작아 보이는 장식도 줄바꿈·접힘·가로 스크롤과 함께 돌아야 해서, 각 기능에 경계 조건이 따라붙었다. sticky scroll #3899, 현재 줄·괄호 #3911, 안내선 #3922, 괄호 색 #3946, 공백 #3951, 괄호 이동 #3954.

글을 입력하고 화면을 복원할 때 (9/23~9/26)

장식과 함께 기본 입력의 결함도 고쳤다. 방향키로 여러 커서를 한곳에 모으면 화면에는 커서 하나만 보이는데 글자가 여러 번 입력되던 문제를 막았다. 줄바꿈된 문서의 ⌘←·⌘→는 먼저 화면 행의 끝에서 멈추고, 논리 줄의 처음·끝으로 바로 가는 ⌃A·⌃E도 연결했다. 그 뒤 ⌃E와 아래 방향키를 조합할 때 줄 끝을 매우 큰 열 번호로 표현해 정수 넘침이 나던 경로까지 고쳤다. 커서 병합 #3941, 행·줄 이동 #3949, 이동 중 넘침 #3956.

복원과 파싱의 순서에도 빈틈이 있었다. 백업 내용으로 문서가 바뀌면 중단됐던 파싱을 초기화해 올바른 코드에 거짓 구문 오류가 뜨지 않게 했다. 접힌 문서의 파싱이 끝나는 순간 해제된 줄 표를 그리던 문제, 탭 복원 중 이미 해제한 형제 Term을 다시 읽던 문제도 고쳤다. 화면이 틀리거나 앱이 죽을 수 있던 경로다. 파싱 초기화 #3886, 접힘 승격 #3933, 탭 복원 #3945.

터미널의 테마·이미지·세션 (9/22~9/24)

터미널 안의 프로그램도 라이브 테마 전환을 알게 됐다. DECSET 2031 / DSR 996을 지원해 구독한 프로그램에 밝은 배경과 어두운 배경 사이의 전환을 통지한다. Maru의 테마가 바뀌어도 Claude Code가 시작할 때 읽은 색 구성을 계속 쓰던 자리다. 같은 밝기 등급 안의 색 변경은 이 프로토콜이 구분하지 않는다. 색 구성 통지 #3857.

분할 화면에서는 포커스가 없는 pane의 kitty 이미지가 사라지던 문제를 고쳤다. 화면에 보이는 pane의 이미지를 함께 모으고, 서로 같은 이미지 ID를 써도 충돌하지 않게 나누며 각 pane 밖은 잘라 낸다. 반대로 보이지 않는 탭의 애니메이션은 프레임 상태만 진행하고 화면 전체를 다시 그리게 하지 않는다. 분할 이미지 #3883, 숨은 애니메이션 #3931.

세션 호스트가 유휴 상태로 내려간 뒤 새 터미널이 앱 내부 PTY로 열리던 문제도 막았다. keep-alive를 켰는데 앱 종료와 함께 새 작업이 죽을 수 있던 경로라, 죽은 host 지정을 치우고 한 번 다시 연결한 뒤에만 폴백한다. 사이드바 대화는 폴더 이름을 흉내 내는 대신 세션 ID로 파일을 찾아 한글·밑줄 경로에서도 읽게 했다. 원격 감시 안내는 저장소가 아닌 폴더와 호스트 자체의 감시 불가를 구분하고, 리소스 팝오버는 정렬 전에 목록을 잘라 합계가 어긋나던 문제를 고쳤다. host 재연결 #3855, 대화 파일 탐색 #3910, 원격 감시 안내 #3889, 리소스 합계 #3841.

보이지 않는 탭이 화면을 다시 그리고 있었다 (9/22~9/24)

이번 성능 작업의 공통점은 이미 아는 사실을 매번 다시 계산하지 않는 것이다. 출력 revision을 불필요한 메타데이터 이벤트에서 빼고, 폰트 생성과 hostname 조회를 재사용했다. 읽을 이벤트가 없음을 아는 경우의 드레인과, 할 일이 없다고 확인된 세션의 펌프도 건너뛰었다. 메타데이터 #3850, 폰트 #3875, hostname #3882, 드레인 #3890, 세션 펌프 #3896.

마지막에는 화면 전체를 다시 그리는 조건을 좁혔다. 보이지 않는 탭의 출력은 사이드바만 갱신하고, 사이드바 입력도 그대로면 그리기 결과를 다시 올리지 않는다. 활성 32세션이 2Hz로 출력하는 계측에서는 전체 재투영 710회 중 627회가 보이지 않는 탭의 출력 때문에 생겼다. 이 변경의 8쌍 A/B에서 기본 출력 환경의 앱 CPU가 약 31% 감소했다. 반면 32개 에이전트의 제목 스피너가 계속 바뀌는 환경은 약 7%였고, 보고 있는 탭이 계속 출력하면 이득이 거의 없었다. 렌더 조건 변경 #3918.

수치는 이 PR에 기록된 해당 부하의 결과다. 앞선 PR의 감소율과 더할 수 없고, 전력이나 입력 지연을 측정한 결과도 아니다. 남은 동기 알림 RPC의 개선안은 아직 설계 기록이다. 남은 RPC 진단 #3893.

줄 번호가 사라진 이유 (9/26)

비교 뷰에서 스크롤하면 좌우 줄 번호가 통째로 사라지는 결함이 있었다. 한쪽이 쓸 수 있는 텍스트 run은 640개인데, 68행의 Zig 코드가 구문 색을 나누느라 649개를 요구했다. 본문이 저장소를 다 쓰고 줄 번호가 받을 몫이 없어진 것이다.

화면의 행·열을 기준으로 필요한 run과 글자 저장소를 컴포넌트가 계산하고, 본문과 줄 번호에 몫을 나눠 주게 바꿨다. 비교·병합 뷰도 각 pane의 크기에 맞춰 나누며, 세션이 저장소를 유지해 다음 프레임에 재사용한다. 이 변경은 텍스트 저장소 부족을 고쳤다. 배경·선택·막대 같은 모든 그리기 명령의 몫까지 다루는 다음 조각 B2는 미착수다. 저장소 수정 #3940, 범위와 후속 계획 #3927.

없는 pane은 다른 사람의 pane이 아니다 (9/24~9/26)

원격 tmux pane을 닫았는데 사이드바 행이 남거나 다시 살아나는 문제가 이어졌다. 행을 지우는 것만으로 끝나지 않았다. 마지막에 찾은 원인은 죽은 pane을 조회했을 때 tmux가 실패 코드 대신 성공과 빈 세션 이름을 돌려준다는 것이었다. 그 빈 값을 다음 조회에 넘기면 현재 세션으로 해석돼 다른 Term으로 이벤트가 갔다.

빈 세션 이름이면 다음 조회를 건너뛰게 했다. 모르는 대상을 살아 있는 아무 대상에 붙이지 않는 규칙이다. 행 정리, 사이드카 정리, 이벤트 귀속은 서로 다른 단계라 각각 고쳐야 했다. 행 정리 #3926, 사이드카 #3952, 오귀속 수정 #3960.

9월 26일 시점

네이티브 편집기 계획의 낡은 “N1 진행 중” 제목도 고쳤다. N1~N5는 제품에 들어왔지만, IME의 replacementRange, 실제 위치를 반영하는 markedRange·selectedRange, 다중 커서의 IME 확정 복제는 남아 있다. 이번 주에는 쓰던 작업으로 돌아오는 경로와 편집기의 일상적인 표현을 채웠고, 문서에도 구현된 범위와 남은 범위를 다시 나눠 적었다. 계획 현황 정정 #3963.