Trix生成の `@mention` ペーストによるエディタークラッシュを修正
Trix/ActionTextで生成された @mention HTML をLexxyにペーストするとエディターがクラッシュする問題が修正されました。JSON.parse() の無条件呼び出しをフォールバック付きのパーサーに置き換えることで、Trix形式とLexxy形式の両方の content 属性を安全に処理できるようになっています。
背景
<action-text-attachment> 要素の content 属性は、LexxyとTrix/ActionTextで異なる形式で格納されます。Lexxyは exportDOM() 内で JSON.stringify を用いてHTMLをJSON文字列として書き出すのに対し、Trix/ActionTextは生のHTMLをそのまま属性値として保存します。
この非互換性が顕在化するのは、TrixベースのActionTextコンテンツからメンションをコピーしてLexxyエディターにペーストする場面です。importDOM() が JSON.parse() を無条件に呼び出すため、<span class="person..."> のような生HTMLが渡されると SyntaxError が発生し、エディター全体がクラッシュしていました。
クロスアプリペースト(Trix → Lexxy)はペースト処理のエッジケースとして従来から存在していましたが、この content 属性のフォーマット差異は見落とされていました。
技術的な変更
src/helpers/storage_helper.js に parseAttachmentContent() ヘルパー関数が追加され、JSON.parse を試みて失敗した場合は生の文字列をそのまま返すフォールバックロジックを実装しています。
// Lexxy exports the content attribute as a JSON string (via JSON.stringify),
// but Trix/ActionText stores it as raw HTML. Try JSON first, fall back to raw.
export function parseAttachmentContent(content) {
try {
return JSON.parse(content)
} catch {
return content
}
}
src/nodes/custom_action_text_attachment_node.js の importDOM() では、JSON.parse() の直接呼び出しをこのヘルパーに置き換えています。
変更前:
nodes.push(new CustomActionTextAttachmentNode({
sgid: attachment.getAttribute("sgid"),
innerHtml: JSON.parse(attachment.getAttribute("content")),
contentType: attachment.getAttribute("content-type")
}))
変更後:
nodes.push(new CustomActionTextAttachmentNode({
sgid: attachment.getAttribute("sgid"),
innerHtml: parseAttachmentContent(attachment.getAttribute("content")),
contentType: attachment.getAttribute("content-type")
}))
ヘルパーを storage_helper.js に配置したことで、同様の変換ロジックが必要になった場合の再利用経路も確保されています。
また、同PRではキャプション入力欄のイベント伝播についても修正が加わっています。copy・cut・paste の各イベントに stopPropagation() が追加され、keydown イベントのストップもEnterキーの処理からキャプションtextarea全体のバブリング抑止へと移動されました。これによりキャプション入力欄がLexicalのコンテンツモデル外で独立したキーボード操作(Ctrl+A、Ctrl+C、Ctrl+X 等)を正しくネイティブ処理できるようになっています。
テストは test/browser/tests/paste.test.js に統合される形で追加されました。Trix形式のメンションHTMLをペーストし、pageerror イベントでJSエラーがゼロであることと <action-text-attachment> が1件レンダリングされることを確認するリグレッションテストになっています。
設計判断
try/catch によるフォールバック方式 が採用されました。content 属性のフォーマットを事前に判定する方法(JSON開始文字による検査など)も考えられますが、JSON.parse 自体を試みてエラーをキャッチする方式は、仕様外のエッジケースも含めて最も広い範囲を安全に処理できます。
修正の粒度も最小限に保たれています。importDOM() の1行を置き換えるのみで、Lexxy形式のコンテンツに対する既存の動作は変わりません。新しいヘルパーは storage_helper.js に置かれており、ノード実装とパース責務が分離された構造になっています。
なお、.claude/skills/bugs-reproducer.md の変更では、バグ再現テストをスタンドアロンの bug_*.test.js ファイルとして残すよう方針が変更されています。実際に本PRのテストは paste.test.js に統合されており、ドキュメントと実装の対応が一致しない点は今後の整理が必要かもしれません。
まとめ
本PRは、JSON.parse() の無条件呼び出しという単純な前提の崩れを、最小限のフォールバックロジックで安全に解消した修正です。LexxyとTrixがどちらも <action-text-attachment> を扱いながら content 属性のエンコーディングが異なるという非互換性を明示的に吸収する層が設けられたことで、クロスアプリペーストの堅牢性が向上しました。