Skip to content

Cut 표현 쓰기 실패 시 브라우저의 후속 삭제를 막는다 #729

Closed

Description

@developer-1px

Goal Anchor

  • Outcome: 지원하는 Web Cut에서 clipboard 표현 확보가 실패하면 브라우저의 후속 삭제로 넘어가지 않고 문서·선택·History를 보존한다.
  • Done: 표현 인코딩/쓰기 실패를 회귀 테스트로 고정하고, 실제 Rich Text Usage의 native Cut 단축키에서 실패 후 문서·DOM 선택·Undo/Redo 보존과 정상 재시도를 확인한다. 공통 Web owner 문서와 이슈 댓글에 이벤트 책임·명세 근거·검증 한계를 남긴다.
  • Don't: Core·Profile·공개 API 형태를 늘리지 않는다. Copy/Paste 및 Cut 미지원·clipboardData 부재·payload 부재의 기존 위임 정책을 바꾸지 않는다. Host나 Rich Text에 별도 우회 구현을 만들지 않는다. OS clipboard와 문서의 원자성이나 이미 쓴 표현의 롤백을 보장하지 않는다.

이어받은 계약

사용자의 “계속해줘 다음 작업”, “merge하고” 요청에 따라 #728을 머지한 뒤 기존 EG-CUT의 실패 시 원본 보존 계약을 이어서 검증한다. #726 설계 초안에도 ‘제거 callback 미호출 외에 event 취소·후속 beforeinput/input·document·History 관찰’이 미검증 질문으로 남아 있다. 이번 이슈는 그 실행 경로의 결함 수정이며 #725의 설계 범위를 변경하지 않는다.

재현된 현재 동작

기준 main: aae30a66159e4ec0649fa94806d55805341cb965.

실제 /editing/rich-text Usage에서 text-editable의 offset 2–4를 선택하고, 테스트에서 DataTransfer.setData의 첫 structured MIME 쓰기를 예외로 실패시킨 뒤 실제 ControlOrMeta+x를 입력했다.

  • Chrome 152.0.7977.83, Firefox 150.0.2, WebKit 26.4 모두 같은 결과.
  • cut은 isTrusted: true, defaultPrevented: false.
  • 이어 beforeinput(deleteByCut)이 발생하고 Rich Text editor가 선택 영역을 제거한다.
  • 여기를 선택하고 직접 입력해 보세요. → 여기선택하고 직접 입력해 보세요.로 canonical document가 바뀌고 Undo가 활성화된다.
  • 직접 호출한 clipboard binding에서는 clipboard.unavailable, 제거 callback 0회지만 preventDefault도 0회다. 기존 단위 검증만으로는 브라우저의 후속 삭제를 발견하지 못했다.

실패는 테스트에서 주입했으며 실제 OS 권한 거절을 재현했다고 주장하지 않는다. 합성 ClipboardEvent로 후속 삭제를 흉내 내지 않았다.

책임 경계

정본은 packages/json-document-web/src/clipboard.ts의 createWebClipboardBinding이다. 지원하는 Cut callback, 사용 가능한 event clipboardData, 확보한 payload가 있는 경우 표현 인코딩/쓰기 전에 native Cut 책임을 확보해야 한다. 쓰기가 끝난 뒤에만 정본 제거 callback을 호출하는 순서는 유지한다. 실패 결과와 이벤트 소유권은 서로 다른 사실이다.

기존 createWebClipboardSurface, Rich Text Web binding 및 사이트 Usage는 이 정본을 소비한다. 기존 공개 export·API reference·Usage·source 등록을 유지하고 실제 경로를 검증한다.

완료 증거

  • 기본 codec와 명시적 representations의 인코딩/쓰기 실패에서 native 이벤트가 취소되고 제거·문서 발행·History 변경이 없다.
  • Cut 미지원, clipboardData/payload 부재, Copy 실패, Paste의 기존 위임/거절 경계가 유지된다.
  • 실제 브라우저에서 실패한 Cut의 후속 삭제가 없고 문서·방향 있는 선택·Undo/Redo가 보존된다. 정상 재시도와 Undo도 동작한다.
  • owning package 문서가 취소 시점과 clipboard 부분 쓰기의 한계를 설명하고 기존 Usage/source 연결을 확인한다.
  • 관련 테스트·typecheck·build·문서 검사와 전체 diff 검토 후 하나의 PR로 제공한다.

읽을 거리

  • W3C Clipboard API — Cut event: 취소하지 않은 editable Cut에는 브라우저의 기본 복사·삭제가 남는다.
  • W3C Clipboard API — Cut action: 이벤트 취소 여부에 따라 기본 삭제 경로가 갈린다. 2026-06-24 Working Draft이며, OS clipboard transaction 보장으로 해석하지 않는다.
  • W3C Input Events Level 2: deleteByCut의 의미와 beforeinput 취소 가능성을 확인할 수 있다. 이번 이벤트 순서의 실행 증거는 위의 세 엔진 재현이다.

Activity

  1. self-assigned this
    on Sep 8, 2026
  2. developer-1px commented on Sep 8, 2026

    @developer-1px
    OwnerAuthor

    작업 계획과 학습 포인트

    Goal Anchor는 본문의 원본·선택·History 보존 계약으로 고정한다. Copy/Paste와 미지원/빈 Cut 위임, 공개 API 형태, OS clipboard 비원자성은 유지한다.

    현재
    Cut → 표현 쓰기 실패 → clipboard.unavailable 반환
                         → 취소되지 않은 native Cut
                           → beforeinput(deleteByCut) → 원본 제거·History 생성
    
    수정할 경계
    Cut → callback·clipboardData·payload 확인 → native Cut 취소
        → 표현 인코딩/쓰기
          ├─ 실패 → 실패 결과, 원본·선택·History 유지
          └─ 성공 → 정본 제거 → 결과
    

    return { ok: false }는 library 호출자에게만 의미가 있다. 브라우저는 그 반환값을 읽지 않으므로 preventDefault()가 별도로 필요하다. ‘실패했으니 다른 owner에게 맡긴다’는 정책은 이미 맡은 파괴적 작업에서는 원본 보존과 충돌한다. W3C Cut action의 취소 분기와 실제 세 엔진 trace를 함께 보면 이 차이를 확인할 수 있다.

    진행 순서:

    1. 기존 Web 실패 테스트에 이벤트 소유권을 추가하고 실제 native Cut 브라우저 회귀 사례를 작성해 수정 전 실패를 확인한다.
    2. 공통 Web binding의 취소 시점만 옮기고 기본 codec·명시적 representations, 실패/거절/성공·위임 경계를 검증한다.
    3. owning README와 기존 API/Usage/source 연결을 확인하고, 세 엔진에서 문서·방향 있는 DOM 선택·History·정상 재시도를 검증한다.
    4. 관련 검사와 전체 diff 검토 후 PR을 열고 완료 증거를 댓글에 남긴다.

    이벤트 취소가 OS 쓰기 성공을 인증하지는 않는다. 이미 쓴 일부 표현은 남을 수 있고, 브라우저 내부의 예외 없는 실패나 시스템 clipboard rollback까지 이 테스트가 보장하지 않는다. 현재 미해결 항목은 구현과 회귀 검증이며 범위 변경은 없다.

  3. developer-1px commented on Sep 8, 2026

    @developer-1px
    OwnerAuthor

    구현 검증: 실패 결과와 native 이벤트는 별도 계약이다

    수정 전 회귀 테스트는 Web에서 8개 모두 preventDefault 0회로 실패했다. 실제 Chrome Cut 회귀 테스트도 canonical document의 여기를 선택하고 직접 입력해 보세요.가 여기선택하고 직접 입력해 보세요.로 바뀌어 실패했다.

    수정은 createWebClipboardBinding 내부에서 supported Cut의 취소를 표현 인코딩/쓰기 앞으로 옮기는 것이다. 공개 함수·옵션·결과 형태는 그대로다. Rich Text나 Host의 beforeinput에 우회 상태를 추가하지 않았다.

    확인한 경계 결과
    기본 codec의 인코딩·첫/두 번째 쓰기 실패 native 취소 1회, 제거 미호출, 문서·selection·history snapshot과 발행 기록 불변
    명시적 representations의 인코딩·첫/중간/마지막 쓰기 실패 같은 보존 계약; 이미 쓴 표현은 남을 수 있음
    Cut 미지원·clipboardData 부재·payload 부재, Copy 실패 기존 위임 유지
    지원하는 Cut/Paste의 편집 거절·미지원 Paste 기존 이벤트 책임 유지
    JSON·HTML·plain text 쓰기 실패 × Chrome·Firefox·WebKit 9개 native Cut 사례 모두 통과

    브라우저 사례는 각자 실제 편집 → Undo로 실행 가능한 Redo를 먼저 만든다. 그런 다음 역방향 선택에서 쓰기 실패를 주입하고 실제 ControlOrMeta+x를 입력한다. 실패 후 문서·DOM 텍스트·canonical/DOM 선택이 그대로이고 Undo/Redo 상태가 유지됨을 확인한다. 기록된 이벤트는 취소된 trusted cut 하나이며 후속 beforeinput(deleteByCut)은 없다. Redo를 실제 실행해 미래 기록도 보존됐음을 확인한다. 실패 주입을 복구한 뒤 정상 Cut → Undo가 원문과 역방향 선택을 복원한다.

    관련 네 패키지 테스트는 20 files / 138 tests 통과했다. 남은 검증은 문서·타입·사이트 검사, 기존 Usage/source 연결 및 전체 diff 검토다.

    공부할 점

    단위 테스트가 callback을 관찰하는 것과 브라우저가 문서를 보존하는 것은 같은 증거가 아니다. 예전 검증에는 다음 화살표가 빠져 있었다.

    library callback 미호출
        + native 기본 동작 취소
        + 후속 이벤트·canonical 문서·DOM 선택·History 관찰
        = 이 경로에서 원본 보존을 확인할 수 있는 증거
    

    Clipboard Cut event는 합성 Cut이 실제 삭제를 수행하지 않는다는 점도 설명한다. 그래서 이 회귀 테스트는 쓰기만 실패 주입하고 Cut 자체는 실제 단축키로 발생시킨다. 명세는 이벤트 모델을 설명하고, 실행 테스트는 현재 세 엔진과 이 Usage에서 그 계약을 확인한다. 이를 모든 OS clipboard 장애의 원자성 보장으로 확대하지 않는다.

  4. developer-1px commented on Sep 8, 2026

    @developer-1px
    OwnerAuthor

    PR 제공 및 완료 증거

    PR: #730 · commit ba1f0ee520e2f7ffa6ff43e19afb3f2bc8bd5def · branch issue-729-cut-failure-ownership.

    • 기본 codec·명시적 representations의 인코딩/쓰기 실패에서 이벤트 취소, 제거 미호출, 문서·선택·History snapshot 및 발행 기록 불변을 확인했다.
    • Cut 미지원·clipboardData/payload 부재·Copy 실패의 위임과 Paste의 기존 거절/위임 경계를 유지했다.
    • 실제 native Cut 실패 9개(3 MIME × 3 엔진)에서 원문·역방향 DOM 선택·실행 가능한 Redo 보존, 후속 삭제 부재, 정상 Cut 재시도 및 Undo 복원을 확인했다.
    • 기존 공개 export/API reference를 유지하고 owning README와 Clipboard Adapter Reference를 갱신했다. Clipboard Usage는 createWebClipboardSurface를 소비하며, 등록된 clipboard.ts 소스와 Web API Reference로 연결됨을 기존 workbench 브라우저 검사에서 확인했다. Rich Text는 같은 binding을 소비하고 Host 우회 구현은 추가하지 않았다.
    • 전체 diff 5개 파일이 본문 계약 또는 문서/검증 증거에 대응한다. 원격 issue/PR 본문과 diff를 다시 읽었고, 원격 PR diff와 로컬 diff도 일치한다.

    검증 결과:

    검사 결과
    관련 4개 패키지 20 files / 138 tests 통과
    두 Web 패키지 typecheck·build 통과
    새 native Cut 실패 브라우저 사례 10 passed(setup 1개 포함)
    기존 Rich Text 브라우저 사례 62 passed / 2 skipped(setup 포함; skip은 Chromium 전용 IME의 다른 엔진 실행)
    Clipboard Usage·workbench source 9 passed(setup 포함)
    사이트 단위 테스트 25 files / 138 tests 통과
    사이트 typecheck·guard 통과
    문서·API reference·workspace 통과; 공개 export 1033개 유지
    Pages build·정적/HTTP 검사 통과

    사이트 단위 테스트의 최초 실행에서는 브라우저 검증과 병행 중 기존 문서/화면 로딩 3개가 대기 시간 제한에 걸렸다. 브라우저 종료 후 --maxWorkers=1로 전체 재실행해 통과했다. 테스트나 timeout 설정을 바꾸지 않았다.

    현재 상태는 pr-open이다. CI의 Plan·External kit는 통과했고 Package·Site는 실행 중이다. 이번 PR의 merge는 아직 진행하지 않았다. 완료된 계약은 위의 동기 Web 이벤트 실패 경로이며, OS 권한 실패의 모든 형태나 clipboard rollback을 보장하지 않는 한계는 그대로다. 승인된 범위 변경은 없다.

  5. developer-1px commented on Sep 10, 2026

    @developer-1px
    OwnerAuthor

    승인된 contract delta: Cut의 입력 소유권과 실행 준비를 분리하는 정본 경계를 구현합니다. Web의 기존 editing-host target 판정을 Clipboard 라우팅에 연결하고, 다른 편집영역은 payload를 읽거나 취소하지 않고 위임합니다. 앱 소유 Cut은 먼저 취소하고 빈 선택·clipboard 없음·쓰기 실패는 소유한 실패로 종료합니다. 모든 표현을 쓴 뒤 동일 캡처 대상을 삭제하며 부모의 중복 처리를 막습니다. 공개 API·소유 문서·Usage·Source와 중첩 입력/실패/중복 처리 검증을 연결합니다. 기존 빈 입력 위임 정책을 보존한다는 이전 항목은 이 승인된 소유권 계약으로 대체합니다.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions