feat: 그랩 팝오버 ⌥⏎ 단순 복사와 라인넘버 드래그 선택 제거 - #69
Merged
Conversation
enableLineSelectionDrag(기본 true)를 추가한다. false면 라인넘버 pointerdown에서 즉시 선택하는 대신 pendingLineSelect 세션으로 미루고, pointermove가 다른 행으로 건너가는 순간 세션을 취소한다 — 그 제스처는 드래그고 드래그는 아무것도 선택하지 않는다. 같은 행 안 지터는 무시하고, 그대로 놓으면 클릭으로 확정한다(한 줄 선택·shift클릭 확장·재클릭 해제, notifySelectionStart/End/Committed 시퀀스까지 기존 pointerdown 경로와 동일 — 분기 본문은 extendSelectionFromShiftClick/ selectSingleLineFromPoint로 공유한다). "+" 버튼 드래그(gutterSelecting)는 그대로다. 기본값이 true라 라이브러리 소비자의 동작은 바뀌지 않는다.
- 뷰어에 enableLineSelectionDrag: false를 적용 — 라인넘버 드래그는 더 이상
줄 선택을 만들지 않는다. 드래그는 코드 텍스트 선택(그랩)의 제스처다.
- ⌥⏎(Option/Alt+Enter)는 프롬프트·펜스·File:/Lines: 머리말 없이 잡은 코드
텍스트만 클립보드로 복사한다(plainSnippet). 성공/실패·자동 닫힘은 제출과
같은 경로를 공유하고 IME 가드도 앞선다. Shift+Enter 개행은 그대로.
- 복사 성공 후 자동 닫힘을 1200ms → 400ms로. 확인("Copied"+초록 체크)으로서
최소 체류만 남긴다(기존 1200은 copyButton RESET_MS와 맞춘 값이었다).
- 팝오버 하단에 ⌥⏎ Copy code only 각주를 상시 고지한다 — .grab-label과
같은 어휘(11px·정수 행고·--vd-fg-muted)에 키 글리프만 #c2c2ca로 밝힌다.
2026-07-30 스펙이 힌트 줄을 지운 건 Enter/Esc 이중 고지 때문이었고, 이번
각주는 새 액션의 유일한 고지 채널이라 겹침이 없다. 상시 줄이 생겼으므로
POPOVER_SIZE.height 190 → 211.
- 유닛 8종·e2e 2종(⑯ 드래그 무선택/클릭 유지, ⑰ ⌥⏎ 순수 복사) 추가,
스펙 문서 작성.
⑯은 "라인넘버 드래그가 아무것도 선택하지 않는다"는 절반만 지킨다. 나머지 절반 — enableLineSelectionDrag:false에서도 클릭 계열 제스처가 드래그 켠 경로와 똑같이 동작한다 — 은 commitPendingLineSelect의 두 분기(shift클릭 확장·재클릭 해제)에 있는데 그 둘을 통째로 지워도 grab e2e 17종이 전부 통과했다(실측). ⑱을 넣은 뒤에야 빨간불이 난다. 선택 개수는 행 수가 아니라 스탬프 수다 — unified는 선택된 행마다 [data-selected-line]을 둘(번호 셀 + 내용 셀) 찍는다(실측). 그래서 한 줄은 2, 세 줄은 6이다. 픽스처의 dragSelect엔 모디파이어 인자가 없어 shift는 스펙에서 keyboard.down/up으로 감싼다. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PaHtxdofJX6EvH7BWXgYKX
포크 패키지 수정은 [diffdeck] 주석으로 upstream 이탈을 코드에 표기하고 CLAUDE.md 예외 목록에 올려야 하는데, enableLineSelectionDrag는 둘 다 빠져 있었다. InteractionManager에 7곳·CodeView에 2곳 마커를 넣고 예외를 4건 → 5건으로 등재한다(회귀망이 ⑯만으로는 미달인 근거도 함께 적는다). 같은 커밋에서 이번 브랜치가 낡게 만든 CLAUDE.md 서술 5곳을 고친다: - 진입 경로 ① — 거터는 이제 드래그 확장만 꺼진 상태다 - 자동 닫힘 1.2초 → 0.4초 - 키 분기에 ⌥⏎ 단순 복사와 "IME → altKey → shiftKey → 제출" 순서 계약 - 고지 채널 둘 → 셋. 새 .grab-keys 각주가 "상시 힌트 줄 제거"와 모순이 아닌 이유와 POPOVER_SIZE 190 → 211 - grab.e2e.ts 15종 → 18종 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PaHtxdofJX6EvH7BWXgYKX
⑨는 "복사 전후 창 높이가 같다"(sr-only 힌트가 창을 늘리지 않는다)를 지키는 유일한 회귀망인데 — happy-dom엔 레이아웃이 없어 유닛이 원리적으로 못 잡는다 — press 뒤에 상태 폴과 boundingBox로 왕복을 두 번 했다. 자동 닫힘이 1200ms일 땐 넉넉했지만 400ms로 줄면서 느린 러너가 그 사이에 팝오버를 닫아 진짜 계약 테스트가 flake가 될 수 있다. 상태와 높이를 한 번의 evaluate로 같은 순간에 읽어 경합을 원리적으로 없앤다. close()가 data-state를 되돌리지 않아(계속 "ok") 닫힌 뒤에도 폴은 통과하므로 hidden을 함께 실어, 닫혔을 때 "69 → 0"이 아니라 닫혔다고 말하게 한다. 주석의 1.2초도 0.4초로 바로잡는다. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PaHtxdofJX6EvH7BWXgYKX
- [diffdeck] 마커를 4곳 더 붙인다(PointerSession 유니온, has ActiveLineSelectionSession, pointerup·pointercancel case, pluck 2곳). 특히 pointerup이 드래그 없는 선택이 실제로 확정되는 지점인데 upstream 코드처럼 보였다. 9곳 → 13곳. - commitPendingLineSelect의 의도적 이탈을 명시한다: extendSelectionFromShiftClick이 false를 낼 때 드래그 켠 경로는 통째로 빠져나가지만 여기선 공통 꼬리가 그대로 돌아 notifySelectionEnd/Committed가 한 쌍 헛돈다. 이 리포 뷰어는 선택 콜백을 하나도 안 물려 무해하지만, 기본값 true의 취지가 "라이브러리 소비자 무영향"이므로 적어 둔다. - 크로스행 취소가 행 인덱스가 아니라 (lineNumber, side)로 키를 잡는다는 사실을 주석에 적는다 — unified에서 한 행이 old/new 번호 셀을 함께 낼 수 있어 가로 지터로도 취소된다. 기존 주석은 "다른 행으로 건너가면"이라고만 적어 이 경우를 설명하지 못했다. - pointerInfo == null 주석이 "앵커 행으로 돌아와 놓는" 경우만 열거해 옆으로 빠져나간 드래그도 앵커 행을 확정한다는 사실을 가렸다. - CLAUDE.md: 팝오버 구성이 여전히 "둘"이라 같은 문단 뒷부분(.grab-keys 상시 줄, POPOVER_SIZE 190 → 211)과 모순이었다 → 셋. - CLAUDE.md: "드래그를 ②로 넘긴다"는 기제 주장 완화 — 거터는 user-select: none이라 거기서 시작한 드래그는 텍스트 선택으로 넘어가지 않고 그냥 아무 일도 하지 않는다(엔진 주석이 근거). - main.ts: 이 브랜치가 다시 쓴 주석 블록의 max-height 96px → 90px(실제 CSS). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PaHtxdofJX6EvH7BWXgYKX
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
실사용 피드백 4건을 한 표면(grab 팝오버 · 거터 선택)에서 함께 반영하고, 그에 딸린
문서·회귀망 의무까지 닫는다. 설계 근거는
docs/superpowers/specs/2026-08-24-grab-plain-copy-and-gutter-drag-off-design.md(로컬 전용)에 있다.무엇이 바뀌나
File:/Lines:머리말 전부 없이 잡은 코드 텍스트만 클립보드로 간다(편집기에 바로 붙여넣는 형식이라 mixed 선택의+/-마커도 싣지 않는다). Shift+Enter 개행은 그대로.⌥⏎ Copy code only각주 상시 고지..grab-label과 같은 어휘(11px · 정수 행고 15px ·--vd-fg-muted)에 키 글리프만#c2c2ca로 밝힌다.구현 메모
enableLineSelectionDrag?: boolean(기본true= upstream 동작 유지)을 추가하고 뷰어만false로 켠다. 핵심은 선택 시점을 옮긴 것이다: pointerdown에서 즉시 선택하던 것을pendingLineSelect세션으로 미루고, pointermove가 다른 행으로 건너가면 세션을 취소한다. 같은 행 지터는 무시하고 그대로 놓으면 클릭으로 확정한다 — "드래그의 첫 행이 선택으로 남는" 반쪽 동작이 생기지 않는다. 분기 본문은extendSelectionFromShiftClick/selectSingleLineFromPoint로 드래그 켠 경로와 공유한다.packages/diffs수정이므로 Foundation 예외다:[diffdeck]마커 9곳(InteractionManager 7 · CodeView 2)을 넣고 CLAUDE.md 예외 목록에 5번으로 등재했다.altKey→shiftKey→ 제출.altKey를 뒤로 미루면 ⌥⏎가 개행으로 새고, IME 가드보다 앞으로 당기면 조합 중 오작동한다.POPOVER_SIZE.height190 → 211(15px 줄 + 6px gap). 이 상수는open()때 한 번만 계산되므로 과소 선언하면 화면 아래쪽에서 카드가 잘린다.Screenshots
dist/cli.js를 실제로 띄워 Playwright로 캡처했다(텍스트 드래그 → 팝오버).main)Test plan
bun testbun run test:coveragegrab/encode.ts·grab/popover.ts포함)bun run typecheckbun run lintbun run format:checkbun run test:e2e신규 회귀망:
grab-popover.test.ts·grab-encode.test.ts): Alt+Enter 복사 · 기본동작 취소 · IME 가드 · 400ms 닫힘 ·.grab-keys렌더 · aria/title 고정값 ·plainSnippetside/mixed.grab.e2e.ts): ⑯ 라인넘버 드래그 무선택 + 클릭 선택 유지, ⑰ ⌥⏎ 순수 코드 복사 + 각주 표시, ⑱ 드래그를 끈 거터의 shift클릭 확장 · 재클릭 해제.⑱은 뮤테이션으로 판별력을 확인했다.
commitPendingLineSelect의 shift클릭·재클릭 해제 두 분기를 통째로 지우고selectSingleLineFromPoint하나만 남겨도 ⑯·⑰을 포함한 grab e2e 17종이 전부 통과했다. ⑱을 넣은 뒤에야 빨간불이 난다.packages/diffs엔markup-contract.test.ts하나뿐이라 유닛이 이 경로를 원리적으로 못 잡는 것도 같은 이유다.알려진 실패 (이 PR과 무관)
e2e 3건(
lockfile-freeze·retokenize-cache·worker-highlight)이 로컬에서 실패하는데, 셋 다 실패 지점이#status의"Loading…"고착이다 — CLAUDE.md에 기록된 기존 Bun$never-settle 행의 재현이다.main(573ab76)에서 같은 스펙을 돌려도 동일하게 실패하는 것을 확인했으므로 이 브랜치의 회귀가 아니다. 이 브랜치는 server·git 서브프로세스 경로를 전혀 건드리지 않는다.🤖 Generated with Claude Code
https://claude.ai/code/session_01PaHtxdofJX6EvH7BWXgYKX