Trix生成の `@mention` ペーストによるエディタークラッシュを修正

basecamp/lexxy

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.jsparseAttachmentContent() ヘルパー関数が追加され、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.jsimportDOM() では、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ではキャプション入力欄のイベント伝播についても修正が加わっています。copycutpaste の各イベントに stopPropagation() が追加され、keydown イベントのストップもEnterキーの処理からキャプションtextarea全体のバブリング抑止へと移動されました。これによりキャプション入力欄がLexicalのコンテンツモデル外で独立したキーボード操作(Ctrl+ACtrl+CCtrl+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 属性のエンコーディングが異なるという非互換性を明示的に吸収する層が設けられたことで、クロスアプリペーストの堅牢性が向上しました。

記事メタデータ

Generated by:
Claude Sonnet 4.6 for DiffDaily
LLM Trace:
33876f8d

この記事はAIによって自動生成されています。内容の正確性については、必ずソースコードやPRを確認してください。

品質レビュー結果

Review Status:
承認済み
Review Count:
1回
Reviewed by:
Gemini 2.5 Pro for DiffDaily

Review Criteria:

記事構成 ✓ PASS

Title, Context, Technical Detailの存在と明確さ

リード文(総論)→背景・技術詳細・設計判断(各論)→まとめ(結論)という「総論→各論→結論」の構成が明確に適用されており、非常に分かりやすいです。

カスタムMarkdown構文 ✓ PASS

シンタックスハイライト・GitHubリンク記法の正確性

ファイル名付きのコードブロック記法(```言語:ファイルパス)およびPR番号のリンク記法([#822](URL))が正しく使用されています。

対象読者への適合性 ✓ PASS

エンジニア向けの適切な技術レベルと表現

使用されている技術用語や説明の粒度が専門知識を持つエンジニア向けに最適化されており、冗長な説明がありません。

パラグラフ・ライティング ✓ PASS

トピックセンテンス・1段落1トピック・段落長

各セクションが総論→各論の構成になっており、各パラグラフがトピックセンテンスで始まるなど、パラグラフ・ライティングの原則が守られています。これにより、高い可読性が確保されています。

Diff内容との照合 ✓ PASS

コードブロックとDiff内容の一致

記事内で引用されているコードブロックは、提供されたDiff情報と完全に一致しています。また、`app/assets/javascript/lexxy.js`におけるイベント伝播の修正など、PRの主題以外の関連変更も正確に捉えられています。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

`action-text-attachment`, `importDOM`, `JSON.parse` などの技術用語が文脈に応じて正確に使用されています。

説明の技術的正確性 ✓ PASS

技術的主張の正確性と論理性

TrixとLexxyにおける`content`属性のフォーマットの違いがクラッシュの原因であるという説明は、PR Descriptionの内容と一致しており、技術的に正確です。

事実の突合 ✓ PASS

PR情報による主張の裏付け(ハルシネーション検出)

記事内のすべての主張は、PRのDescriptionやDiff内のコードによって裏付けられています。特に、PR内のドキュメント変更と実装の不一致を指摘する分析は、単なる情報抽出を超えた深い洞察を示しており、ハルシネーションの懸念はありません。

数値・固有名詞の確認 ✓ PASS

PR番号・コミットID・バージョン等の正確性

PR番号(#822)やファイルパス(`src/helpers/storage_helper.js`など)が正確に記載されています。

タイトル・説明との一致 ✓ PASS

記事タイトル・説明とPR内容の一致

記事のタイトル「Trix生成の`@mention`ペーストによるエディタークラッシュを修正」は、PRのタイトル「Fix pasting Trix @mentions crashing the editor」の内容を的確に反映しています。

外部知識の正確性 ✓ PASS

PRに記載のない外部知識(LTS、サポート状況など)の不使用

記事には、PR情報に基づかない外部知識(バージョンサポート状況、リリース日程など)は含まれていません。

時間表現の正確性 ✓ PASS

時間表現がPR情報と一致しているか

「修正されました」「クラッシュしていました」といった時間表現は、PRの内容と照らして正確に使用されています。