Skip to content

편집 문법의 공통 규칙과 Hands profile을 적합성 테스트로 연결한다 #721

Description

@developer-1px

Goal Anchor

  • Outcome: 같은 Hands profile을 소비하는 제품이 선택·편집·복사·복원에서 같은 관찰 가능한 의미를 얻도록, 승인한 편집 문법 설계를 실행 가능한 적합성 증거로 연결한다.
  • Done: 아래 설계의 공통 규칙·profile 선택·기존 owner를 유지하면서 대표 Document·Sheet·Rich Text 공개 API binding과 owner별 적합성 사례를 구현하고 문서에서 규칙→profile→binding→검증 결과를 추적한다.
  • Don’t: Core API와 기존 동작을 바꾸지 않는다. 새 범용 editor, 전역 command union/bus, capability/profile registry, package hierarchy를 만들지 않는다. 모든 Hands·브라우저·독립 구현의 상호운용성을 인증하지 않는다.

사용자가 설계 개선 후 “진행해”로 실행을 승인했다. 아래는 승인한 설계 원문이다. 설계 문서의 Done은 설계 산출물의 기준이며, 이 실행은 그 적합성 설계를 기존 owner에서 구현하는 후속 단계다. 이름 변경이나 미지원 명령 추가는 포함하지 않는다.

현재 #719 / PR #720이 소유한 세션 오류 복구·구독·협업 History 변경은 별도 작업으로 유지한다.

승인한 설계와 결정 원문

편집 문법의 안정화 설계

상태: Design Draft. 기존 public API와 Stable profile의 의미는 유지한다.
이 문서는 공통 편집 규칙의 소유자, Hands별 해석, 적합성 증거를 설계한다.
아래 제안의 구현 완료나 외부 상호운용성을 주장하지 않는다.

목표와 범위

오랫동안 정착한 편집 문법을 구현과 제품이 바뀌어도 유지되는 계약으로 만든다.
작은 Core는 장기간 약속할 수 있는 의미만 소유한다. JSON Document의 여섯
member와 편집 문법의 크기는 서로 다른 문제다.

  • Outcome: 같은 Hands profile을 소비하는 제품은 선택·편집·복사·복원에서 같은
    관찰 가능한 의미를 얻는다.
  • Done: 이 설계에서 공통 규칙과 profile 선택을 구분하고, 각 규칙의 기존 owner,
    현재 구현과의 차이, 이를 판정할 적합성 사례를 연결한다.
  • Don't: 이번 설계로 Core API를 늘리거나 기존 동작을 변경하지 않는다. 새로운
    범용 editor, command bus, profile registry나 package hierarchy를 만들지 않는다.

구현 근거의 범위는 JSON Document, Selection, Editing, Affordance, Web과
Document·Sheet·Rich Text의 대표 편집 경로다. Object의 전체 선택과 Database의
cut 미지원은 profile 차이를 판정하는 사례로 포함한다. 모든 Hands의 완성도,
브라우저별 입력 일치, 협업 wire protocol의 수렴은 이 설계의 검증 결과가 아니다.

현재 문제와 원인

EditingSession은 value·selection·history를 함께 처리하고 Selection family는
대상의 전이를 공유한다. 그러나 EditingIntent의 공통 계약은 type: string
dispatch 형태이며, 동사의 사전 조건과 결과는 editor별 코드와 문서에 있다.
Official Hands도 아직 호환성 약속을 확정하지 않은 후보다.

이 때문에 여러 editor가 같은 API 형태를 제공해도 같은 편집 문법을 따른다는
증거가 되지 않는다. selectAllAffordance의 전체 선택 토글처럼 특정 관습이
범용 기본값으로 보이고, selection.move처럼 문서 변경과 선택 변경이 이름에서
구별되지 않는 사례도 있다. 동작을 선언하는 규칙과 그 규칙을 검증하는 사례의
연결이 부족한 것이 원인이다.

세 종류의 계약과 기존 소유자

다음은 새 runtime 계층이 아니라 기존 책임들이 약속할 내용의 구분이다.

계약 고정할 의미 정본 소유자
공통 편집 규칙 선택 전이, 편집의 원자성, 복사와 History의 관계 Selection·Editing
Hands profile 편집 대상, 범위 해석, 삭제·붙여넣기 결과, 지원 작업 Document Type과 해당 editor의 기존 owner를 조합한 Hands
입력 매핑 키·포인터·IME를 어떤 편집 의도로 해석하는가 Adapter·Affordance와 framework Connector
keyboard / pointer / IME ─ Adapter·Affordance ─┐
직접 호출하는 프로그램 ──────────────────────┤
                                             ↓
                         해당 editor의 편집 의도
                         + Selection / Topology
                                             ↓
                         EditingPlan + selectionAfter
                                             ↓
                         EditingSession → JSONDocument.commit
                                             ↓
                         해당 편집의 snapshot / History

이는 대표적인 값 변경 흐름이다. Copy는 조회이며, 선택만 바꾸는 작업은 document
commit을 만들지 않고, Undo/Redo는 선택한 History owner를 실행한다. 순수 JSON
소비자에게 Editing이나 Hands 설치를 요구하지 않는다.

기존 owner 계속 소유할 책임 다른 곳으로 넘기지 않을 결정
json-document JSON 값·주소·검증·원자적 Patch·변경 알림 selection, clipboard, undo step을 Core에 넣지 않음
json-document-selection key/range/mask family, transition·map·reconcile·targets DOM focus와 문서 mutation은 각각의 owner가 처리
json-document-editing EditingPlan, snapshot, transaction, local History, external History 연결 같은 transaction·History를 각 Hand에서 다시 구현하지 않음
기존 Document Type/editor 모듈 유효한 대상·문서 연산·projection, 의도를 plan으로 변환 Host가 붙여넣기·삭제의 의미를 다시 결정하지 않음
json-document-affordance 선택·활성화·취소의 입력 의미, gesture lifecycle 좌표·DOM capture는 Web에 연결
json-document-web 및 text Web Adapter platform event, clipboard 교환, native editing lifecycle semantic edit는 해당 editor API로 전달
React 등 Connector framework subscription·render·focus lifecycle 문서·선택·History의 별도 상태 소유자를 만들지 않음

Document Type은 책임 이름이며 이 표로 package 재배치를 승인하지 않는다. 현재
Document·Sheet 등은 Editing에, Rich Text는 자신의 package에 구현돼 있다.
Hands는 기존 공개 API들의 함께 검증된 조합이다. Host는 제품 데이터·권한·copy·
layout·concrete external instance와 조합을 소유한다.

공통으로 고정할 편집 규칙

아래 ID는 설계 요구사항이다. 실제 동결은 owner의 versioned 계약과 적합성
증거를 통해 이루어진다. DOM API 모양, JSON 저장 shape, 특정 키 조합은 이
규칙의 전제가 아니다.

ID 관찰 가능한 규칙 Owner
EG-SELECT 선택만 바꾸는 작업은 document value와 문서 Undo/Redo 기록을 바꾸지 않는다. 범위 확장은 유효한 기존 anchor를 보존하고 focus를 이동한다. Selection·Editing
EG-TARGET navigation 위치, 선택된 대상, native text caret을 구별한다. 문서 안에서 대상이 이동했을 때 identity를 유지하는지와 삭제 후 선택의 도착점은 해당 profile이 정한다. Selection·해당 editor
EG-EDIT 값 변경은 patch와 selectionAfter를 한 편집 결과로 발행한다. 실패한 의도는 자신의 partial mutation·selection·History entry를 남기지 않는다. Editing
EG-COPY Copy는 현재 profile의 선택 대상을 구조화된 payload와 교환 표현으로 읽으며 document·selection·History를 바꾸지 않는다. 해당 editor
EG-CUT Cut은 복사할 대상을 확정한 뒤 그 대상에 대한 제거 plan을 실행한다. payload 확보 실패 시 제거하지 않는다. 제거 실패 시 문서는 유지되며 성공한 cut으로 보고하지 않는다. Editing·해당 editor·Web
EG-PASTE Paste는 profile이 정한 위치·대체 범위·identity 규칙으로 한 편집을 실행하고 결과 Selection을 함께 정한다. 호환되지 않는 payload와 범위 초과의 처리를 profile에 명시한다. 해당 editor·Editing
EG-HISTORY 한 사용자 작업의 Undo 단위를 명시한다. local History는 다른 변경이 개입하지 않은 undo/redo에서 기록된 document와 Selection을 복원한다. selection-only·no-op은 새 문서 History entry를 만들지 않는다. Editing·선택한 History owner
EG-GESTURE 구조적 gesture의 preview는 아직 확정되지 않은 결과다. commit은 그 preview에 해당하는 작업을 한 번 실행하고 cancel은 이를 실행하지 않는다. Affordance·해당 editor 연결
EG-RESULT 각 편집 결과와 발행 snapshot은 그 편집의 value·selection·history 상태를 함께 설명한다. 재진입이나 외부 변경으로 더 최신 상태가 생겨도 이전 결과의 의미를 덮어쓰지 않는다. Editing

EG-EDIT의 실패 보장은 해당 의도가 만든 효과에 적용한다. 이미 받아들인 외부
document 변경을 실패한 의도 때문에 되돌리지 않는다. 이를 확인할 때 외부
변경 동기화와 새 의도 실행의 관찰 시점을 분리한다.

EG-CUT은 OS clipboard와 문서 사이의 분산 transaction을 약속하지 않는다.
Headless cut()은 보존 가능한 payload를 반환하고, Web event binding은 payload를
쓴 뒤 제거를 실행한다. Clipboard 쓰기 실패, 제거 거절, 성공한 후의 Undo를
서로 다른 결과로 검증한다. 브라우저가 custom edit를 다시 실행하지 않도록 하는
event ownership은 Web이 소유한다.

EG-HISTORY에서 외부 변경 이후의 의미는 선택한 History 계약을 따른다. 현재
local inverse History는 이를 비우며, collaboration History는 내 기여를 선택적으로
되돌린다. 둘을 같은 복원 알고리즘으로 고정하지 않는다. 이미 존재하는
EditingHistory를 사용하고 여러 Hand가 공유하는 작업 단위도 그 owner가 정한다.

EG-GESTURE는 구조 편집 preview에 대한 규칙이다. IME의 중간 DOM mutation과
composition grouping은 DOM 편집 lifecycle의 별도
계약을 따른다. createGestureSession의 존재만으로 Host의 preview가 문서를
변경하지 않는다고 증명할 수 없으므로 실제 연결까지 검증한다.

Hands profile이 반드시 결정할 내용

각 profile은 다음 질문에 하나의 답 또는 명시적인 설정별 답을 제공한다. 이는
문서와 적합성 사례의 항목이며 새로운 runtime descriptor type은 아니다.

  1. 대상: 무엇을 편집하며 어떤 identity와 위치 단위를 사용하는가?
  2. 선택: 사용할 family, primary의 의미, 여러 범위 처리, focus와 선택의 관계는?
  3. Topology: 가시 순서와 전체 대상 집합 중 무엇을 각 작업에 사용하는가?
  4. 작업: Select, Insert, Delete/Clear, Copy, Cut, Paste, Undo/Redo 중 무엇을
    지원하며, 미지원 작업은 왜 해당 profile의 사용 목적과 양립하는가?
  5. 결과: 삭제 후 선택, 붙여넣기 위치·대체 규칙, 범위 초과, 새 identity는?
  6. History: typing·composition·drag의 작업 경계와 외부 변경의 처리 방식은?
  7. 입력: 선택한 플랫폼 관습, 편집 중인 text field와 구조 탐색의 우선순위는?

unsupported와 일시적으로 unavailable인 상태를 구별한다. Cut이 없는 editor와
선택이 없어 Cut을 할 수 없는 editor는 같은 계약이 아니다. API나 UI에서 지원을
선언한 작업은 동일한 지원 범위의 적합성 사례를 가져야 한다. 미지원 선언만으로
기대되는 기본 편집 흐름을 생략할 수 없으며, 그 선택의 외부 관습이나 제품 목적을
profile에서 설명한다.

현재 구현을 설명하는 대표 매핑

이 표는 현재 동작의 관찰이다. 관찰된 기본값이 곧 영구히 고정할 규칙은 아니다.

항목 Document Sheet Rich Text
편집 단위 stable ID를 가진 블록과 text stable row/column ID의 셀 stable node ID와 text/child point
Copy 대상 선택된 블록 전체 primary rectangle 선택된 structured slice
Paste 기본적으로 마지막 선택 블록 뒤에 삽입 focus 셀부터 고정 경계 안에 기록 선택 구간과 schema에 맞게 slice 삽입
제거 선택 블록 제거 Cut은 primary rectangle 값을 null로 비움 선택 구간 제거·schema 제약 적용
후속 선택 삽입 블록 범위, 삭제 후 남은 이웃 붙여넣은 직사각형, Cut은 기존 선택 transform 후 mapping된 text/child point

현재 binding은 각각 json-document-editing/src/document.ts, sheet.ts,
json-document-rich-text/src/editor.ts에 있다. Document의 text offset이 블록
Copy를 부분 문자열 Copy로 바꾸지는 않는다. Sheet의 primary rectangle 정책도
여러 범위 전체의 Copy와 구별한다. 이런 차이는 profile 이름과 사용법에서 드러나야
하며, 공통 함수 이름 때문에 사용자가 동일한 대상을 예상하게 해서는 안 된다.

구현에서 계약으로 옮길 때의 결정

현재 증거 설계 결정 동결 전 확인할 증거
Document selection.move는 블록을 옮김 navigation과 content movement를 의미 어휘에서 구별한다. 현재 identifier 변경은 별도 binding 변경으로 다룬다. 위치 이동은 document 불변, 블록 이동은 identity 보존·Undo 복원
selectAllAffordance는 Mod+A 재입력 시 clear Select All의 공통 의미는 대상 전체 선택이다. 재입력 토글은 이를 선택한 입력 profile의 매핑으로만 취급한다. 같은 select-all 의도의 반복은 같은 선택; 토글 profile은 두 번째 키 입력을 clear 의도로 변환
Database cut 부재가 테스트에 고정됨 현재 binding의 미지원으로 기록한다. 이를 모든 Database의 영구 문법으로 일반화하지 않는다. Database profile에서 Cut 생략 근거 또는 지원 동작을 정한 뒤 admission 판정
historyGroup과 external History가 공존 작업 묶음 정책과 기록·복원 메커니즘을 구별한다. external History owner가 step을 결정한다. 같은 drag/composition 사례를 선택한 History owner에 연결한 결과
EditingIntenttype: string 공통 contract test로 의미를 고정하고 실제 호출은 각 editor의 기존 API로 번역한다. dispatch, copy, cut, undo, redo를 각 owner public API로 실행

새로운 전역 command union이나 capability registry는 이 문제를 해결하는 전제가
아니다. 이미 다른 의미를 가진 호출들을 한 이름으로 합치면 대상과 결과의 차이가
숨는다. 공통 protocol은 공유하는 규칙에 두고, 문서의 의미는 기존 owner에 둔다.

적합성 설계

테스트의 단위는 함수 유무가 아니라 시작 상태 → 의미 있는 작업 → 관찰 결과다.
Owner package의 테스트 harness가 public API에 작업을 연결한다. Harness는 자체
편집 로직을 구현하지 않고 호출과 결과의 정규화만 수행한다.

공통 rule의 vector와 runner는 해당 owner의 tests/conformance/에 두고, 장르별
fixture와 API binding은 해당 editor owner에 둔다. 기존 Rich Text versioned
vectors와 Core suite는 원래 owner와 경로를 유지한다. 기존 unit test의 기대값을
복사하는 대신 공통 rule에 해당하는 사례를 공용 runner로 승격한다.

사례 작업 관찰할 결과 현재 근거와 추가 검증
선택 후 확장 A 선택 → C까지 확장 anchor A 유지, profile topology의 대상, document·History 불변 Selection range tests → 여러 editor의 같은 rule binding
Copy의 무변경성 선택 → Copy 두 번 같은 의미의 payload, value·selection·Undo/Redo 불변 clipboard surface test의 payload 확인에 상태 불변 관찰 추가
편집과 복원 선택 → 허용된 변경 → Undo → Redo 각 단계의 value·selection·availability, snapshot 순서 session history tests → Document·Sheet·Rich Text public binding
거절의 원자성 경계 밖 Paste 또는 schema 거절 해당 작업의 partial value·selection·History·notification이 남지 않음 Sheet와 Rich Text rejection 사례를 같은 관찰 계약에 연결
Cut의 실패 경계 clipboard 쓰기 실패 / 제거 거절 전자는 제거 미호출, 후자는 문서 불변·실패 결과 Web clipboard rejection tests에 쓰기 실패 사례 연결
Gesture 취소 begin → preview 여러 번 → cancel 확정 문서·History 불변, gesture 비활성 gesture unit test와 실제 editor 연결을 함께 검증
반복 전체 선택 select-all 두 번 같은 범위의 선택 유지 KeySelection rule과 입력 profile의 토글 매핑을 별도로 검증
외부 변경 선택·편집 후 외부 삭제 → 읽기/구독 profile의 유효한 선택, 선택한 History owner의 상태 external-selection/session tests와 History binding 구분

각 사례는 document, selection, History availability/대상, 발행 snapshot, clipboard
결과를 관찰한다. JSON-equal value, profile의 논리적 대상과 작업 순서를 비교한다.
Object identity, 내부 cache, snapshot 객체의 정확한 key 집합은 적합성 기준으로
추가하지 않는다. Failure code와 optional field는 owning contract의 규칙을 따른다.

새 identity를 생성하는 작업은 ID 문자열 자체의 일치를 요구하지 않는다. 입력에
이미 있던 ID는 정확히 보존하고, 새 ID만 결과 사이의 일대일 대응으로 비교하며
문서·Selection·clipboard 내부 참조에도 같은 대응을 적용한다. Harness의 이 정규화는
잘못된 대상 선택, identity 재사용, 순서·내용 손실을 숨겨서는 안 된다.

통과 보고서는 rule → profile → public binding → vector → 결과를 추적할 수 있어야
한다. 공용 runner가 한 구현의 내부 model을 요구하거나 Host가 기대 결과를 만들기
위해 edit를 재구현하면 이 설계가 실패한 것이다. 여러 장르의 구현을 통과한 결과는
규칙의 적용 가능성 증거이며 같은 profile의 독립 구현 간 상호운용 증거와 구별한다.

장기 호환성

약속하는 것은 고정된 profile의 지원 입력과 관찰 가능한 결과다. 같은 profile
revision 아래에서 target·selection·Undo 경계·failure/fallback 의미를 바꾸지 않는다.
새 API나 기능이 필요하면 기존 소비자의 결과를 보존하는 추가 계약으로 제공한다.

  • Profile 식별과 version은 정본 문서에 우선 둔다. 모든 JSON payload에 공통
    envelope나 version field를 추가하지 않는다. 기존 Rich Text profile URI 같은
    저장 계약은 그 owner가 유지한다.
  • 지원 입력을 넓혀 기존의 거절·fallback을 성공 처리로 바꾸는 것도 호환성 검토
    대상이다. 의미를 보존하지 못하면 별도 profile revision으로 제공한다.
  • 모호함이 발견되면 서로 다른 해석의 반례를 먼저 남긴다. 명확한 기존 규칙 위반은
    구현 수정으로, 기존 규칙이 여러 동작을 허용했다면 profile 변경으로 판단한다.
  • 이전 profile의 vectors와 실제 소비자 binding을 보존해 새 구현에서도 실행한다.
    Current tests를 새 동작에 맞춰 일괄 수정한 것은 호환성 증거가 아니다.
  • 구현 알고리즘·cache·DOM rendering은 이 결과를 보존하는 범위에서 바꿀 수 있다.

Stable admission에는 공통 rule과 profile의 필수 작업이 모두 실행되고, 각 책임의
public API·owner reference·site Usage·source registration이 연결돼야 한다. 같은
profile의 독립 구현, 특히 명세 작성자 밖의 구현 경험에서 생긴 해석 차이를 해결해야
장기 상호운용성의 근거가 된다. 사이트의 한 Demo나 내부 테스트 통과로 이를 대체하지
않는다.

비용과 재검토 조건

이 설계는 profile별 선택과 compatibility corpus를 유지하는 비용을 만든다.
그 비용을 줄이기 위해 실행 구조를 하나로 통일하는 대신 현재 owner의 runner를
공유하고, 실제 지원하는 profile만 약속한다. 모든 장르에 한 Selection shape를
강제하는 대안은 text range·grid rectangle·object set의 의미를 잃으므로 채택하지
않는다.

다음 관찰은 설계를 다시 검토할 근거다.

  • 공통 rule이 기존의 타당한 편집 관습을 표현하지 못하면 해당 rule을 profile
    선택으로 내리거나 사전 조건을 바로잡는다.
  • 한 역할의 구현이 여러 Host에 남으면 기존 owner API의 부족 여부부터 확인한다.
  • 규칙을 검증하려고 Core에 편집 상태를 넣어야 한다면 owner 분리가 잘못된 것이다.
  • 같은 profile의 두 구현이 모두 suite를 통과해도 소비자 결과가 다르면 누락된
    관찰 항목을 반례로 추가한다.

외부 근거

  • Microsoft 표준 Edit 메뉴:
    Undo/Redo·Cut/Copy/Paste·Select All·Delete의 정착된 명령 어휘. 모든 장르의
    정확한 selection·paste·history 알고리즘을 규정하는 자료는 아니다.
  • W3C APG Keyboard Interface:
    focus와 selection의 구별, 예측 가능한 navigation. APG는 구현 지침이다.
  • W3C APG Listbox:
    복수 selection model과 선택 가능한 Ctrl+A 토글은 입력 관습이 하나가 아님을 보여준다.
  • Input Events Level 2:
    물리 입력과 편집 의도의 구분을 참고한다. Working Draft의 event API를 영구 계약으로
    채택하지 않으며 구조 편집 전체의 의미로 확대하지 않는다.
  • RFC 9413:
    확장과 오류 처리를 명확히 규정하고 해석 차이를 유지보수로 해결하는 근거다.
  • W3C Implementation Experience:
    명세 작성자 이외의 구현과 실제 상호운용 경험을 별도 증거로 요구하는 근거다.

Activity

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

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions