コードブロックへの貼り付け時に空行で内容が外へ飛び出すバグを修正

basecamp/lexxy

コードブロックへのペースト操作中に空行(連続する改行)が含まれていると、コンテンツがコードブロック外のパラグラフに分割されてしまう問題が修正されました。$hasUpdateTag(PASTE_TAG) によるガード処理を追加し、ペースト操作中は基底クラスの安全な動作に委譲するようにしたことで、通常のEnterキーによる脱出動作には影響を与えずにバグを解消しています。

背景

Lexicalエディタは、ペースト操作の各改行ごとに insertNewAfter() を呼び出す仕組みを持っています。EarlyEscapeCodeNode はコードブロックからカーソルを抜け出させる独自のエスケープロジックをこのメソッドに実装しており、空行(連続する2つの改行)を含むテキストをペーストした際に問題が発生していました。

ペースト中に空行によって最終行が空になると、EarlyEscapeCodeNode のエスケープロジックが意図せず発火し、コンテンツをコードブロック外のパラグラフに分割してしまっていました。これはペースト操作の途中でエスケープ判定が行われることに起因しており、ユーザーがコードブロックを意図的に抜け出そうとした操作ではないにもかかわらず脱出が発生するという誤動作です。

この問題は、Lexicalがペースト操作を複数回の insertNewAfter() 呼び出しに分解して処理するという実装特性と、EarlyEscapeCodeNode 独自の脱出ロジックの組み合わせによって引き起こされていました。

技術的な変更

修正は src/nodes/early_escape_code_node.js への最小限の変更で実現されています。$hasUpdateTag および PASTE_TAGlexical からインポートし、insertNewAfter() の冒頭にペースト操作の検出ガードを追加しました。

変更前:

insertNewAfter(selection, restoreSelection) {
  if (!selection.isCollapsed()) return super.insertNewAfter(selection, restoreSelection)

  if (this.#isCursorAtStart(selection)) {
    return this.#insertParagraphBefore()
  } else if (this.#isCursorOnWhitespaceOnlyLastLine(selection)) {
    return this.#insertBlankLineBelow(selection, restoreSelection)
  }

変更後:

insertNewAfter(selection, restoreSelection) {
  if ($hasUpdateTag(PASTE_TAG) || !selection.isCollapsed()) {
    return super.insertNewAfter(selection, restoreSelection)
  } else if (this.#isCursorAtStart(selection)) {
    return this.#insertParagraphBefore()
  } else if (this.#isCursorOnWhitespaceOnlyLastLine(selection)) {
    return this.#insertBlankLineBelow(selection, restoreSelection)
  }

$hasUpdateTag(PASTE_TAG) が真の場合、独自のエスケープロジックをすべてスキップし、基底クラスである CodeNodeinsertNewAfter() に処理を委譲します。CodeNode の基底実装はコードブロック内への改行挿入を維持するため、ペースト中の不正なコードブロック脱出が防止されます。既存の !selection.isCollapsed() 条件は同じ分岐にまとめられ、どちらの場合も super への委譲という一貫した挙動になっています。

合わせて test/browser/tests/paste/paste_into_code_block.test.js が新規追加され、以下の2つのシナリオをPlaywrightで検証しています:

  • 既存コードがあるコードブロックへ空行を含むテキストを貼り付けた場合
  • 空のコードブロックへ空行を含むコードを貼り付けた場合

どちらのテストでも、貼り付け後に <code> 要素が1つだけ存在し、コードブロック外に不正なパラグラフが生成されていないことを assertEditorContent で検証しています。

設計判断

Lexicalの $hasUpdateTag / PASTE_TAG API を使って更新コンテキストを判別するアプローチが採用されました。Lexicalはペースト操作を特定のタグ付きトランザクションとして実行するため、このAPIによってペーストかどうかを正確に判定できます。

別の修正アプローチとして、エスケープ条件の判定ロジック自体を修正する方法も考えられますが、ペーストコンテキストの検出という明確な責務分離の観点から、今回のガード方式は直感的です。ペースト中は EarlyEscapeCodeNode 固有のロジックを一切実行しないという明確な境界が引かれており、エスケープ機能の通常動作(Enter2回でコードブロックを抜ける)には何ら影響しません。

まとめ

ペーストコンテキストを $hasUpdateTag(PASTE_TAG) で検出し、基底クラスに委譲するというわずか4行の変更で、ペースト操作とインタラクティブ操作の責務を明確に分離することができました。Lexicalのトランザクションタグという既存の仕組みを活用したことで、エスケープ機能の既存動作を維持しながら、最小限の変更でバグを根本解決しています。

記事メタデータ

Generated by:
Claude Sonnet 4.6 for DiffDaily
LLM Trace:
b32e350f

この記事は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リンク記法の正確性

ファイル名付きのシンタックスハイライト(```javascript:ファイルパス)とGitHubのPRリンク記法が正しく使用されています。

対象読者への適合性 ✓ PASS

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

Lexicalエディタの内部実装に関する専門的な内容であり、対象読者であるエンジニアに適した技術レベルと表現で記述されています。

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

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

各セクションが総論→各論の構成になっており、各段落はトピックセンテンスで始まるなど、パラグラフ・ライティングの原則が守られており、非常に読みやすいです。

Diff内容との照合 ✓ PASS

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

記事内で引用されているコードブロック(変更前・変更後)は、提供されたDiff情報と正確に一致しています。ファイルパスも正確です。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

「$hasUpdateTag」「PASTE_TAG」「EarlyEscapeCodeNode」など、Lexicalフレームワークに関連する技術用語が正確かつ適切に使用されています。

説明の技術的正確性 ✓ PASS

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

バグの原因(ペースト時の`insertNewAfter`呼び出しとエスケープロジックの干渉)や修正内容(`PASTE_TAG`によるガード)の説明が、PRのDescriptionやDiffと整合しており、技術的に正確です。

事実の突合 ✓ PASS

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

記事内のすべての主張は、PRのタイトル、Description、Diffの内容に基づいており、根拠のない推測や憶測(ハルシネーション)は含まれていません。「設計判断」セクションはPRに明記されていませんが、コードの意図を解説する妥当な範囲の内容です。

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

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

PR番号(#1009)が正確に記載されています。

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

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

記事のタイトルはPRのタイトル(Fix pasting text with blank lines breaking out of code blocks)の内容を日本語で正確に要約しており、主題が一致しています。

外部知識の正確性 ✓ PASS

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

PR情報に記載のないバージョン情報やリリース予定など、外部知識の追加はなく、提供された情報源に忠実です。

時間表現の正確性 ✓ PASS

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

完了した変更について「修正されました」という過去形の表現が使われており、時間的な表現は正確です。