← 목록
기타 2026-07-11 11KB 읽기 11분

CrownyAI PPM 터미널 사용성 v2 — 순간에코 + 스크롤백 선택/복사 (2026-07-11)

개요

크라우니 입력도구 v1(2026-07-11-크라우니-입력도구-v1.md) 이후 사용자가 실측한 두 가지 터미널 통증을 해소했다.

  • A. 타이핑 순간 에코: 키 입력마다 전체 렌더 파이프라인(bash+crownyc 서브프로세스,
실측 90~290ms)을 돌던 것을, 입력코어(~10ms)만 즉시 실행하고 화면 표시는 네이티브 오버드로우로 다음 프레임(수 ms)에 보여준 뒤, 마지막 키 400ms 후 1회만 전체 렌더하도록 바꿨다(디바운스 코얼레스).
  • B. 스크롤백 선택·복사: 출력 줄 클릭으로 선택(Shift+클릭=범위), ⌘C(입력 선택 없으면
스크롤백 선택 폴백)/⌘⇧C(전체 스크롤백)로 pasteboard에 복사할 수 있게 했다.

A. 순간 에코 + 디바운스 렌더

설계

  • CrownyAppView마지막키시각(NSDate)·디바운스타이머(NSTimer) 프로퍼티 추가.
  • injectKey:/injectJamo:/조합 중 BS/캐럿 내비게이션(←/→/Home/End) 이 기존
[self renderAndReload] 즉시호출 대신 [self noteKeyTypedAndScheduleRender] 호출로 통일:
  • 마지막키시각 = now
  • setNeedsDisplay:YES (다음 프레임에 drawRect 오버드로우만 반영, 수 ms)
  • scheduleDebouncedFullRender (0.4s 단발 NSTimer, 재호출 시 이전 타이머
  • invalidate → 연속 타이핑은 자연히 코얼레스, 마지막 키 400ms 후 1회만 실제 렌더 파이프라인 실행)
    • Enter(실행 버튼 경로)·마우스 클릭 액션(버튼/포커스 등)은 기존 그대로 즉시
    renderAndReload/reload — 디바운스 대상 아님.

    오버드로우(drawTypingOverdraw)

    • drawRect:에서 self.rep(마지막 PPM)을 그린 위에, service=="터미널"이고
    타이핑 세션 활성(마지막 키 400ms 이내)일 때만 포커스된 입력칸 영역을 네이티브로 덧그린다: 다크 배경(16,14,12) · 텍스트(230,225,210) · 캐럿/선택 골드 계열 (201,169,97 / 46,57,46 톤 셀렉션) — 어두운입력칸() 팔레트와 동일.
    • 13pt 모노스페이스 시스템 폰트로 근사(정본 CIF 벡터 폰트와 완전히 동일하지는
    않으나, 디바운스 렌더가 도착하면 즉시 정본으로 교체됨 — 순간 표시용).
    • 순수 헬퍼로 분리(프로세스 스폰 없음, 파일 I/O만):
    appTypingOverdrawActive · appFocusedInputName · appReadInputField · appInputRectForName · appCanvasRectToView — 모두 --selftest-inputv2 하네스로 검증.
    • 현재 범위는 터미널의 다크 입력칸만(어두운입력칸()). 다른 앱의 흰 입력칸(입력칸())
    으로 확장하려면 팔레트 분기가 필요 — v2 범위 밖으로 명시.

    알려진 트레이드오프

    디바운스 타이머가 0.4s에 발화한 뒤 실제 렌더 파이프라인이 도착하기까지 추가로 90~290ms가 걸린다. 그 사이(마지막 키 후 약 0.49~0.69s 구간의 마지막 0.09~0.29s)는 오버드로우 조건(<0.4s)이 이미 꺼졌고 self.rep도 아직 갱신되지 않아, 화면이 짧게 "타이핑 직전 상태"로 보일 수 있다. 스펙에 명시된 설계 그대로이며, 그 직후 렌더가 도착해 정상 표시된다.

    B. 스크롤백 선택·복사

    클릭 선택

    • mouseDown:에 새 분기 추가: service=="터미널" 이고 클릭 캔버스좌표
    cy >= 44(출력영역 시작, 터미널앱.한선 상수 미러 kTermOutputY)이며 어떤 히트영역(appAnyHitRect, 입력/버튼 등)에도 속하지 않으면 스크롤백 클릭으로 처리 — 기존 kAppEventToau 프로세스 스폰(VM 이벤트루프 조회) 전에 가로채 빈 영역 클릭마다 VM을 띄우지 않는 부수 이득도 있다.
    • 표시행 = (cy - 44) / 19(라인높이, kTermLineHeight — 터미널앱.한선 :169~170
    상수 미러 주석). termSelectLineIndex:extend:/tmp/터미널선택.psv시작|<표시행>\n끝|<표시행>\n 기록. Shift+클릭은 기존 시작(앵커)을 유지한 채 만 갱신.
    • 터미널앱.한선_선택값("시작"/"끝")으로 읽어 스크롤백 루프에서 선택 범위에
    든 표시행에 박스(0, 출력y-2, CVW, 라인높이, 36,32,28) 배경을 텍스트보다 먼저 그린다.
    • PPM 픽셀 검증: 선택 표시행(row2, y=80~99)은 (36,32,28), 비선택 행은
    배경색 (10,9,8) 그대로 — bbox·색상 정확히 일치 확인.

    복사

    • copy:(⌘C) → cb_copyGeneric: 입력칸에 선택이 있으면(입력코어 COPY 응답의
    SEL| 파싱) 기존대로 그 텍스트를 pasteboard로. 없으면(또는 포커스 자체가 없으면) service=="터미널"일 때 /tmp/터미널선택.psv를 읽어 스크롤백 선택 텍스트로 폴백.
    • cut:(⌘X)은 기존 입력-전용 동작 그대로(스크롤백은 읽기전용 로그라 폴백 없음).
    • copyAllScrollback:(⌘⇧C, 신규 Edit 메뉴 항목 "Copy All Scrollback") — 선택
    무관하게 /tmp/터미널상태.psv의 "줄|" 전체를 pasteboard로.
    • 스크롤백 텍스트 추출은 네이티브 순수 헬퍼(appTerminalOutputLines ·
    appReadTermSelection · appTermScrollbackSelectionText)로 분리 — 프로세스 스폰 없이 파일만 읽어 즉시 복사(pasteboard 실제 쓰기는 빌드로만 확인, 텍스트 추출 로직은 하네스로 검증).
    • "지우기" 액션(버튼/명령 둘 다) 시 /tmp/터미널선택.psv도 함께 비움
    (터미널상태.한선 _선택비움()) — 낡은 표시행 참조 방지.

    셀프테스트 하네스

    CrownyBrowser --selftest-inputv2 — GUI 없이 실행, 위 순수 헬퍼 9종 + 인스턴스 메서드 2종(termSelectLineIndex:extend:, scheduleDebouncedFullRender 코얼레스)을 실제 프로덕션 코드로 직접 검증(19/19 PASS). /tmp/입력상태.psv·히트맵.psv· 터미널상태.psv·터미널선택.psv(라이브 세션과 공유하는 실제 경로)를 테스트 전후 백업/복원해 다른 세션과의 공존을 배려했다 — 실행 후 복원 확인 완료.

    검증 요약

    1. --selftest-inputv2 19/19 PASS (오버드로우 판정·입력필드 파싱 2종·히트맵
    rect 조회 2종·좌표변환·스크롤백 라인 파싱·선택 읽기·선택 텍스트 추출(정순/역순/ 범위밖)·Shift 확장 선택·디바운스 코얼레스).
    1. 실제 입력코어.toau FOCUS→KEY×N→LEFT×2 시퀀스로 /tmp/입력상태.psv
    이름|값|캐럿|선앞|선뒤 5필드로 정확히 갱신됨을 확인(오버드로우 파서 입력과 정합).
    1. 스크롤백 선택 하이라이트 PPM 픽셀 검증(선택행 36,32,28 / 비선택 10,9,8 정확
    일치).
    1. TERM_ACTION=지우기(자가치유 경유 터미널액션.sh 지우기) → /tmp/터미널선택.psv
    0바이트로 비워짐 확인.
    1. 터미널 echo E2E: FOCUS→KEY×N("echo 입력도구완성")→TERM_ACTION=실행
    /tmp/터미널상태.psv$ echo 입력도구완성 + 입력도구완성 정확히 기록, 입력값 초기화 확인(v1 대비 무회귀).
    1. 회귀 스모크: 계산기(계산기렌더.sh)·메모(뷰어렌더.sh) 둘 다 exit0 + 유효
    PPM.
    1. 빌드: make web exit0(신규 경고 0), make public universal crownyonly
    3변종 전부 exit0. otool -L WebKit 링크수: public=1·universal=1· crownyonly(Crowny-Full)=0·main=1 — 기존과 동일(무회귀).
    1. 3곳 배치(scratch 경유 mv, 제자리 cp 금지 규칙 준수): native/CrownyBrowser,
    CrownyAI.app/Contents/MacOS/CrownyBrowser, CrownyBrowser.app/Contents/MacOS/CrownyBrowser SHA1 전부 동일 98ea4ceecaabfaa317dc5ba599fb04c9b60d7d59.
    1. 터미널앱.한선·터미널상태.한선 수정분 hanseonc_high 단독 컴파일 exit0
    확인(가져오기 경로 CROWNY_STD 지정).

    크라우니코드 보고

    • lookup-first 조회: crownycode-learn.sh search "터미널 스크롤백 선택 복사" /
    "디바운스 렌더 오버드로우" / "스크롤백 선택 하이라이트" → 전부 MISS(0건).
    • 생성: 오버드로우/디바운스/스크롤백선택 네이티브 헬퍼(ObjC, crowny-browser.m)
    + 터미널앱.한선 _선택값()/선택 하이라이트 렌더 + 터미널상태.한선 _선택비움().
    • 학습: crownycode-learn.sh add 터미널스크롤백선택하이라이트 "<코드>" 완료.
    • 크라우니코드: lookup HIT 0건(재사용) / MISS 3건(생성) / learn 추가 1건
    (ObjC 네이티브 헬퍼는 한선씨 학습DB 대상 밖 — ObjC 자체가 이 작업의 "부득이한 경우"에 해당하는 기존 CrownyAppView/AppKit 통합층이라 한선씨 전환 대상이 아님, 기존 v1 관례와 동일).

    수정 파일

    • /Users/ef/CrownyBrowser/native/crowny-browser.m — 순수 헬퍼 9종(오버드로우
    판정·입력필드 파싱·히트맵 rect 조회 2종·좌표변환·anyHit·스크롤백 라인/선택 파싱 2종) + CrownyAppView: 마지막키시각/디바운스타이머 프로퍼티, noteKeyTypedAndScheduleRender/scheduleDebouncedFullRender/ drawTypingOverdraw, injectKey:/injectJamo:/캐럿내비/조합BS 디바운스 전환, mouseDown: 스크롤백 클릭 분기 + termSelectLineIndex:extend:, copy:cb_copyGeneric(스크롤백 폴백)+cbWriteStringToPasteboard:, copyAllScrollback:(⌘⇧C 메뉴 항목 추가), dealloc에 디바운스 타이머 정리, --selftest-inputv2 셀프테스트 하네스(main() 진입점 분기).
    • /Users/ef/CrownyBrowser/src/터미널앱.한선_선택값() 읽기 함수, 스크롤백
    루프에 선택 표시행 하이라이트 박스.
    • /Users/ef/CrownyBrowser/src/터미널상태.한선_선택비움(), 두 "지우기"
    분기(버튼 액션 + 지우기 명령)에서 호출.

    잔여 이슈 / 다음 단계

    1. GUI 실측(사용자 몫): 타이핑 시 순간 에코가 실제로 보이는지, 클릭 선택·
    Shift 범위선택·⌘C/⌘⇧C 복사가 실제 터미널 앱에서 체감상 매끄러운지는 헤드리스로 재현 불가 — CrownyAI 앱을 열어 터미널 탭에서 직접 확인 필요 (가드레일에 따라 이번 작업에서 GUI 프로세스를 직접 기동하지 않았다).
    1. 오버드로우 폰트 근사: 13pt 시스템 모노스페이스가 정본 CIF 벡터 폰트와
    픽셀 단위로 동일하지 않다(0.4~0.69s 사이 짧은 순간만 노출, 곧 정본으로 교체됨) — 필요 시 CIF 글리프 폭 테이블을 네이티브에도 미러링해 더 정밀하게 맞출 수 있음(v2 범위 밖).
    1. 다른 앱으로 오버드로우 확장: 입력칸()(흰 배경) 팔레트 분기를 추가하면
    메모/계산기 등에도 순간에코를 적용할 수 있음 — 이번 요청은 터미널 한정이라 범위 밖으로 뒀다.
    1. Shift+드래그(마우스 드래그로 실시간 범위 선택): 이번 구현은 클릭/
    Shift+클릭만 지원(스펙 그대로). mouseDragged: 핸들러 추가로 확장 가능 (입력도구 v1의 "Shift+화살표 드래그 선택 미구현"과 같은 패턴의 잔여 항목).
    1. 분할뷰 동시 터미널 2개일 때 스크롤백선택.psv 전역 공유: 캐럿상태.psv와
    동일한 기존 제약(v1 문서에 이미 기록) — 필요 시 탭별 파일 분리로 해결.