크라우니 입력도구 v1 — 구현 완료 (2026-07-11)
개요
사장님 확정 스펙(2026-07-11-크라우니-입력도구-스펙.md)에 따라 터미널 탭에서 실측된
Cmd 조합키 무동작·캐럿/I빔 부재 결함을 해소하고, 캐럿·선택·클립보드를 지원하는 공용
입력 컴포넌트(입력코어.한선)를 신설했다. 한영 OS추종 세션(같은 날 완료, crowny-browser.m
충돌 회피 완료 확인 후 착수) 이후 진행.
실측 결함 진단 (근본원인)
- Cmd+V/C/X/A 무동작: crowny-browser.m 메뉴바 Edit 메뉴(
native/crowny-browser.m
cut:/copy:/paste:/selectAll: 를 nil-target(첫 리스폰더
체인 디스패치)으로 이미 갖고 있었다. CrownyAppView가 앱탭 활성화 시 첫 리스폰더가
되는데(makeFirstResponder:t.appView, :5223 부근) 이 4개 액션 메서드를 구현하지
않아 ⌘C/V/X/A 키 이퀴벌런트가 performKeyEquivalent 단계에서 메뉴에 먼저 소비되고
keyDown 까지 오지도 못한 채 조용히 무시됐다(진단 그대로 확인 — 별도의 keyDown 기반
Cmd 가로채기는 애초에 도달 불가능해 무의미하다고 판단, 표준 Cocoa 관례대로 4개
액션 메서드를 구현하는 것이 유일하게 유효한 수정).
- 캐럿/I빔 부재: 애초에
/tmp/입력상태.psv에 캐럿 개념 자체가 없었다(값 문자열만
기존 터미널 학습 (스펙 5항 — 설계 반영)
- crowny-terminal-native/src/app.m (PTY 백엔드 네이티브 터미널): 진짜
NSTextView를
isCursor → bg를 밝은 회색으로 반전, :906~910)이며 ANSI CSI ?25h/l(모드25)로
가시성만 토글한다 — CrownyBrowser의 PPM/cmd-IR 캔버스에는 대응하는 네이티브 텍스트뷰가
없어 이 접근을 그대로 못 쓴다(직접 캐럿바+선택하이라이트를 그려야 하는 이유).
- crowny-terminal/web/index.html (웹, xterm.js): 테마 객체가
cursor/cursorAccent/
selectionBackground(반투명 accent 색, 예: rgba(46,204,113,.25)) 키를 갖고
cursorBlink:true 옵션을 쓴다 — 얇은 바 캐럿 + 반투명 틴트 선택 하이라이트 관례.
이번 구현의 캐럿바(2px 수직바)·선택 하이라이트(옅은 톤 사각형) 설계가 이 관례와
가장 가깝다. 단 크라우니 VM의 _프림_사각형은 알파블렌딩이 없어(불투명 RGB만) 반투명
틴트 대신 미리 섞인 톤 색상(라이트: rgb(250,224,204), 다크: rgb(70,58,28))으로
근사했다.구현
1. 입력코어.한선 (신규 — /Users/ef/CrownyBrowser/src/입력코어.한선)
키입력처리.한선(FOCUS|KEY|BS, /tmp/입력상태.psv 이름|값 2필드)의 KEYCMD 계약을 그대로
두고(그 파일은 무수정 — mouseDown의 FOCUS 기록 경로가 계속 사용), 상태 파일을
이름|값|캐럿|선앞|선뒤 5필드로 확장하는 새 처리기. 캐럿/선택 필드가 없는 줄은 캐럿=값끝·
선택없음으로 기본 해석되어 완전 하위호환.
명령: FOCUS|이름, KEY|c(캐럿 위치 삽입, 선택 있으면 치환), BS(캐럿 앞 삭제/선택
삭제), LEFT/RIGHT(선택 있으면 그 경계로 접기), HOME/END, SELALL, PASTE|텍스트,
COPY/CUT(표준출력 SEL|<선택텍스트>), CLICKCARET|localX,h(근사 글자폭 스캔으로
캐럿 인덱스 역산, 크기=h/3·최소8).
2. crowny-browser.m (CrownyAppView)
copy:/cut:/paste:/selectAll:4개 표준 액션 메서드 구현 — Edit 메뉴가 이제
paste:는 NSPasteboard →
NFC 정규화 → PASTE 명령. copy:/cut:은 COPY/CUT 명령의 stdout SEL| 라인을
파싱해 NSPasteboard에 씀.
appRunInputCoreCmd()— 입력코어.toau 호출 헬퍼(appRunKeyCmd와 동일 NFC 파일 계약).injectKey:가키입력처리.toau대신입력코어.toau로 라우팅되도록 변경(캐럿이
keyDown:에 ←/→/Home/End 캐럿 내비게이션 추가 — 부수 수정: 기존엔 이 키들이
injectKey되어 버퍼에 오염 문자가 삽입되는
잠재 버그가 있었다(이번에 함께 수정).
mouseDown:에 CLICKCARET 전처리 추가 —appInputHitRect()(히트맵.psv 역순 스캔,
resetCursorRects오버라이드 — 히트맵의 "입력" 종류 rect를 뷰 좌표로 역스케일해
addCursorRect:cursor:[NSCursor IBeamCursor]. mouseDown의 캔버스→뷰 역스케일과
대칭인 정스케일 공식 사용. renderAndReload 완료 시 invalidateCursorRectsForView:
호출로 매 렌더마다 갱신.
- 캐럿 깜빡 — 0.5s
NSTimer(캐럿타이머),/tmp/캐럿상태.psv에 on/off 기록 후
viewDidMoveToWindow에서 시작/정지(activateTabAtIndex가 매번 비활성
탭뷰를 컨테이너에서 통째로 removeFromSuperview 하므로, 이 훅이 "탭 활성화=창에
붙음/비활성화=창에서 떨어짐"과 정확히 일치 — 별도 활성탭 추적 불요). 매 틱마다
appHasFocusedInput()로 포커스 유무 확인 후 없으면 재렌더 스킵(0.1s 비용 회피).3. 화면.한선 (렌더 확장 — 기존 서명 유지)
_입력상태읽기(키)가 확장 필드(캐럿|선앞|선뒤)를 절단해 "값"만 반환하도록 수정
_입력상태원시읽기(신규, 절단 없음)로
캐럿/선택 파싱.
_입력캐럿읽기/_입력선앞읽기/_입력선뒤읽기/_캐럿켜짐(신규) — 확장 필드 없으면
_선택하이라이트/_캐럿바(신규, 화면.한선+터미널앱.한선 공용) — 포커스 필드가
입력칸()(공용, 흰 배경)과 어두운입력칸()(터미널 전용,
다크)에 각각 배선. 함수 시그니처 변경 없음(내부에서 상태를 직접 읽으므로
기존 호출부 무수정).4. 터미널상태.한선 / 한글조합기.한선 / 키입력처리.한선 (방어적 수정)
/tmp/입력상태.psv를 읽는 세 곳(_psv읽기/_상태읽기×2)이 모두 값 뒤 확장 필드를
절단하지 않으면, 입력코어가 5필드로 승격한 줄을 읽을 때 캐럿/선택 숫자가 명령/조합
텍스트에 섞여 들어간다(실측 재현 — 아래 검증 참조). 세 함수 모두 화면.한선과 동일한
"첫 | 뒤 나머지에서 다음 | 앞까지만" 절단 로직을 추가해 방어. 순수 2필드 줄(기존
동작)은 절단 지점이 없어 무변화.
⚠️ 알려진 제약: 한글조합기.한선의 _상태갱신(쓰기 측)은 여전히 2필드로만 기록한다
— 한글 조합 중에는 그 필드의 캐럿이 매 자모마다 "끝"으로 재설정된다(조합은 원래도
끝에서만 진행되므로 기능 저하는 아니지만, 조합 도중 캐럿을 중간으로 옮겨둔 상태를
보존하지 못함). v1 범위 밖으로 문서화만 하고 다음 라운드로 이관.
검증 (헤드리스 E2E)
- 입력코어 단독 KEYCMD 시퀀스 —
FOCUS|터미널입력→KEY|가KEY|나KEY|다
LEFT LEFT(캐럿 3→1) → KEY|X(중간삽입 → "가X나다") → SELALL(0,4) →
PASTE|짱(선택치환 → "짱", 캐럿1) — 매 단계 /tmp/입력상태.psv 필드 정합 확인,
전부 기대값과 일치. COPY(선택없음→SEL|, 전체선택→SEL|짱abc)·CUT(값 비움+
SEL|반환)·BS(선택삭제/캐럿삭제/빈값 3분기)·HOME/END·CLICKCARET(0→0,
9999→끝 클램프, 중간값→가까운 글자경계)까지 전부 검증.
- 하위호환: 2필드 레거시 줄(
레거시필드|안녕하세요, 캐럿 필드 없음)에LEFT
KEY|! →
"안녕하세!요"(끝에서 한 칸 앞 삽입) 정상.
- 렌더 — 캐럿 깜빡 픽셀 diff:
/tmp/캐럿상태.psvon/off 두 프레임을 터미널
- 렌더 — 선택 하이라이트 픽셀 diff: 선택있음(2,4) vs 선택없음(4,4) 프레임 비교
- 회귀 스모크: 계산기(
계산기렌더.sh)·메모(뷰어렌더.sh) 둘 다 exit0 + 유효
- 터미널 실행 E2E: FOCUS(마우스클릭 시뮬)→CLICKCARET 생략(끝 삽입 경로)→
echo 입력도구완성을 글자 단위 KEY| 커맨드로 타이핑(입력코어 경유, 기존
키입력처리 아님)→"실행" 액션 → /tmp/터미널상태.psv 스크롤백에 `$ echo
입력도구완성 + 출력 입력도구완성` 정확히 기록, 입력값 정상 초기화 확인. 이
과정에서 터미널상태.한선의 값-절단 수정이 실제로 파이프(|) 오염을 막는지도
함께 재현·확인(수정 전 재현 시 sh: 17: command not found 형태로 캐럿숫자가
셸 파이프로 오인되는 것을 직접 목격 → 수정 후 정상).
- 빌드:
make webexit0(신규 경고 0 — 기존 무관 경고 2건만), `make public
3변종 전부 exit0. otool -L` WebKit 링크수 재확인:
crownyonly=0, public/universal/main=1(회귀 없음).
- 3곳 배치: 스크래치 임시경로 경유 mv(제자리 cp 금지 규칙 준수) → `native/
, CrownyAI.app/Contents/MacOS/CrownyBrowser`,
CrownyBrowser.app/Contents/MacOS/CrownyBrowser SHA1 전부 동일:
8f4e2426dace203451d8f33281e951815be7e521.
- 자가치유 컴파일 배선:
뷰어렌더.sh(메모)·터미널렌더.sh·터미널액션.sh의
입력코어 추가(기존 키입력처리가 있던 곳마다 동일하게) —
/tmp/입력코어.toau 삭제 후 각 스크립트 단독 실행으로 자동 재컴파일 확인.크라우니코드 보고
- lookup-first 조회:
crownycode-learn.sh search "캐럿 선택 입력코어"/
"커서 캐럿 텍스트편집" → 둘 다 MISS(0건, 학습DB/패턴DB/의미어색인 전부 무매치).
- 생성:
입력코어.한선전체 신규 작성(1건, 캐럿/선택/버퍼 통합 엔진). - 학습:
crownycode-learn.sh add 입력코어캐럿선택버퍼 "<전체코드>"→ 학습완료. - 크라우니코드: lookup HIT 0건(재사용) / MISS 1건(생성) / learn 추가 1건.
하위호환 확인 요약
- 키입력처리.한선 — 무수정, FOCUS 경로 계속 사용(mouseDown).
- 화면.한선
입력칸()/ 터미널앱.한선어두운입력칸()— 함수 시그니처 무변경. /tmp/입력상태.psv— 2필드 줄(값만)은 캐럿=끝·선택없음으로 자동 해석, 어떤
- 계산기/메모 등 캐럿 없는 기존 입력칸 — 렌더 스모크로 무회귀 확인.
수정/신규 파일
/Users/ef/CrownyBrowser/src/입력코어.한선(신규)/Users/ef/CrownyBrowser/native/crowny-browser.m(CrownyAppView: 상수·헬퍼·
/Users/ef/CrownyBrowser/src/화면.한선(입력상태 파싱 하위호환화 + 캐럿/선택
입력칸() 배선)
/Users/ef/CrownyBrowser/src/터미널앱.한선(어두운입력칸()배선)/Users/ef/CrownyBrowser/src/터미널상태.한선, `/Users/ef/CrownyBrowser/src/
, /Users/ef/CrownyBrowser/src/키입력처리.한선` (값-절단 방어)
/Users/ef/CrownyBrowser/native/뷰어렌더.sh,터미널렌더.sh,터미널액션.sh
- 배포:
native/CrownyBrowser,CrownyAI.app/Contents/MacOS/CrownyBrowser,
CrownyBrowser.app/Contents/MacOS/CrownyBrowser (SHA1 동일 배치)잔여 이슈 / 다음 단계
- GUI 실측(사용자 몫): 실제 클릭으로 캐럿이 정확한 글자 사이에 놓이는지,
- 한글 조합 중 캐럿 보존 안 됨(위 "알려진 제약" 참조) — 조합 완료 후에는
- Shift+화살표 드래그 선택 미구현 — v1 명령 목록(LEFT/RIGHT/HOME/END/SELALL)에
- 분할뷰(split) 모드에서 캐럿 타이머 2개 동시 구동 — 두 탭이 동시에 창에
/tmp/캐럿상태.psv(전역 파일)를 두 타이머가 각자 토글해 깜빡임 위상이
충돌할 수 있음(기능 손상 아님, 사소한 시각 흠). 필요 시 탭별 캐럿상태 파일
분리로 해결.
- 적용 2단계(메모 등 기존 입력칸 앱 전환): 이미
입력칸()이 범용적으로
입력코어가 없으면(계산기
등, 원래 키입력처리도 없었음) /tmp/입력코어.toau가 다른 서비스(메모/터미널)
실행으로 이미 빌드돼있어야 동작 — 기존에도 동일한 암묵적 전역 공유 의존성이
있었으므로 신규 회귀는 아님. 견고화하려면 각 서비스 렌더sh에도 입력코어 추가.
- 적용 3단계(문서/프리젠/스프레드 작성기): 아직 미착수(해당 앱 자체가 미존재
입력코어.한선 + 화면.한선 캐럿/
선택 렌더)를 그대로 재사용하면 되도록 설계했음(공용 _선택하이라이트/_캐럿바,
입력칸() 시그니처 무변경).