LinkedIn이 조용히 ProseMirror에서 Quill로 마이그레이션했으며, Composer를 건드린 모든 Browser Automation Tool을 망가뜨렸다.

발행: (2026년 5월 3일 PM 04:34 GMT+9)
8 분 소요
원문: Dev.to

Source: Dev.to

충돌

증상은 구체적이었습니다. 내 MCP 서버의 safari_fill 도구—React Fiber를 순회하고 editor.commands.setContent(html)을 호출하여 ProseMirror를 충실히 채우던—가 이제 헬퍼 데몬을 충돌시키고 contenteditable에 닿는 순간 바로 작곡가 대화 상자를 닫아버렸습니다.

같은 작곡가 URL. 겉보기엔 같은 DOM 트리. 같은 선택자. 하지만 아래에 있는 편집기는 달랐습니다.

DOM은 진실을 말한다

브라우저 콘솔에 들어가서 평소처럼 탐색해 보았습니다:

const el = document.querySelector('[contenteditable="true"]');
el.editor // -> undefined
el.closest('.ProseMirror') // -> null
el.closest('.ql-editor') // -> 

바로 그곳에 있었습니다. .ql-editor는 Quill의 정식 클래스 이름입니다. LinkedIn은 2026년 초에 별다른 공지 없이 포스트 작성기를 ProseMirror에서 Quill로 교체한 것으로 보입니다.

왜 충돌했는가

Quill은 ProseMirror와 마찬가지로 텍스트를 contenteditable그냥 넣는 것을 허용하지 않는다. 두 편집기 모두 내부 모델을 가지고 있는데, Quill은 이를 Delta 라고 부르며, DOM은 그 모델의 하위 흐름이다.

모델을 우회하고 DOM에 직접 쓰면 두 가지 일이 발생한다:

  • 모델과 DOM이 일치하지 않는다.
  • 다음 사용자‑주도 이벤트(키 입력, 저장 등)가 발생하면 재‑렌더링이 수행되는데, 차이가 일관되지 않아 예외가 발생한다.

이 때문에 컴포저가 죽었다. 내 코드는 innerText에 쓰고 있었고, Delta 상태는 에디터가 아직 비어 있다고 판단했으며, React 트리는 이를 조정하려고 시도했고, 대화 상자는 사라졌다. Swift 데몬은 연쇄적인 예외를 포착하고, 이를 위해 스스로 충돌했다.

해결 방법: Quill이 기대하는 방식대로 구동하기

Quill은 프로그래밍 API를 제공합니다. 인스턴스에 대한 참조만 있으면 됩니다. 제가 찾은 탐색 순서는 다음과 같습니다:

  1. .ql-container 클래스를 가진 조상을 찾아 올라갑니다.
  2. .__quill을 시도합니다 — Quill 2.x는 인스턴스를 바로 그곳에 붙입니다.
  3. React Fiber로 폴백: fiber 체인을 따라 올라가면서 memoizedProps.quill 또는 stateNode.quill을 찾습니다 (LinkedIn은 Quill을 React 컴포넌트로 감싸고, 인스턴스를 props에 보관합니다).
  4. 아직도 없으면 실제 CGEvent Cmd+V 붙여넣기로 폴백합니다 — Quill은 isTrusted: true인 클립보드 이벤트를 존중합니다.

인스턴스를 확보하면 실제 삽입은 한 줄이면 됩니다:

quill.setContents([{ insert: text + '\n' }], 'api');

'api' 소스 플래그가 핵심입니다. 이는 Quill에 “이것은 여러분 자신의 API에서 온 것이니 모델과 DOM을 함께 업데이트하라”는 의미를 전달합니다. 텍스트가 커밋되고, Delta가 일관성을 유지하며, React 부모는 손상된 모델에 대해 다시 조정하려 하지 않습니다.

이 글이 플랫폼 자동화에 대해 가르쳐 준 것

편집기는 안정적인 인터페이스가 아니다. ProseMirror와 Quill은 서로 다른 API, 서로 다른 상태 모델, 그리고 “실제 편집으로 간주되는 것”에 대한 서로 다른 규칙을 가지고 있다. 그 중 하나를 목표로 하는 것은 플랫폼이 더 이상 그렇게 하지 않기로 결정할 때까지만 작동한다. LinkedIn은 변경 로그 없이 이 교체를 진행했다. 내가 알게 된 유일한 방법은 내 코드가 깨졌다는 것이었다.

DOM은 최소 공통 분모이고, 편집기 모델이 실제 모델이다. contenteditable에 이벤트를 합성하는 모든 자동화 도구는 진실보다 한 단계 아래에서 동작한다. 때때로 그것은 작동한다(편집기가 이를 조정하기 때문에). 때때로 작동하지 않는다(편집기가 충돌하거나 입력을 조용히 무시하기 때문에). 견고한 방법은 항상 편집기 인스턴스를 찾아 그 API를 호출하는 것이다.

세 번째, 더 불편한 교훈: 현재 헤드리스 환경에서 LinkedIn의 모달 열기 동작이 별도로 깨져 있어 LinkedIn에서 내 수정을 완전히 검증할 수 없었다. 작성자 버튼은 클릭을 받지만, 대화 상자 DOM은 생성되지만 시각적으로는 열리지 않는다. 그래서 Quill 감지는 구현돼 있고 테스트 페이지에서도 검증됐지만, LinkedIn 전용 실시간 경로는 아직 내가 해결하지 못한 별도의 모달 문제에 의해 차단돼 있다.

요약

서드파티 리치 텍스트 편집기(예: Slack, LinkedIn, Discord, Medium, Notion)에 텍스트를 입력하는 무언가를 구축하고 있다면, 편집기 식별자는 해당 플랫폼과의 계약의 일부이며, 플랫폼이 그 안정성을 보장하지는 않습니다. 런타임에 편집기 유형을 감지하고, 알 수 없는 경우에 대비한 대체 방안을 마련하세요(가능하면 실제 클립보드 이벤트를 사용). 발견한 내용을 로그에 남겨 두면, 변경이 발생했을 때 밤 11시 Slack 메시지 대신 자체 텔레메트리를 통해 알 수 있습니다.

그리고 contenteditable 요소의 클래스 리스트를 먼저 확인하세요. ProseMirror와 Quill은 서로 다른 클래스 서명을 가지고 있으며, DOM이 어떤 편집기를 다루고 있는지 알려줄 것입니다—묻기만 하면 말이죠.

이 문제는 safari-mcp@2.10.2에서 수정되었습니다. 소스는 GitHub에서 확인할 수 있습니다.

0 조회
Back to Blog

관련 글

더 보기 »