Prevent spurious `lexxy:change` event on initial load

basecamp/lexxy

Lexxy now prevents the lexxy:change event from firing when the editor loads its initial value that is re‑serialized or sanitized, by initializing the internal form value from the serialized editor content.

背景

lexxy:change は内部フォーム値が変化したときにディスパッチされますが、初期ロード時にサニタイズ前の HTML が #setInternalFormValue に渡されると、サニタイズ後の値との差分が検出されて不要なイベントが発火していました。特に、<del><s> に正規化されたり、<script> タグが除去されたケースで顕在化します。これにより、ページロード直後に期待しない lexxy:change がリスナーへ届き、フォームの状態管理が乱れる可能性がありました。PR #1095 はこの誤検知を根本的に解消することを目的としています。

技術的な変更

新しいヘルパー #readSanitizedEditorValue

変更点: src/elements/editor.js にプライベートメソッド #readSanitizedEditorValue を追加し、エディタからサニタイズされた HTML を取得して null 可能な形で返します。

#readSanitizedEditorValue(editor = this.editor) {
  return editor?.read(() => {
    return sanitize($generateHtmlFromNodes(this.editor, null))
  }) ?? null
}

このメソッドは従来 value ゲッタで直接呼び出していたロジックを抽象化し、テストやコールバックからも再利用できるようにしました。

value ゲッタの単純化

変更前cachedValue を取得する際にインラインで sanitize($generateHtmlFromNodes…) を実行していましたが、変更後#readSanitizedEditorValue に委譲するだけになりました。

- return this.cachedValue ??= this.editor?.read(() => {
-   return sanitize($generateHtmlFromNodes(this.editor, null))
- }) ?? null
+ return this.cachedValue ??= this.#readSanitizedEditorValue()

この変更はロジックの重複排除と可読性向上を目的としています。

初期化フローの再構成

$initialEditorState コールバック内でサニタイズ設定を先に行い、続いて初期値をロードし、最後に内部フォーム値をサニタイズされた結果で上書きします。#configureSanitizer の呼び出し位置を従来の #createEditor から移動し、早すぎても遅すぎても正しく適用されないというタイミング問題を解消しました。

- $initialEditorState: (editor) => this.#loadInitialValue(editor)
+ $initialEditorState: (editor) => {
+   this.#configureSanitizer(editor)
+   this.#loadInitialValue(editor)
+   this.#setInternalFormValue(this.#readSanitizedEditorValue(editor))
+ },

この手順により、エディタが生成したノードはサニタイズ済みの HTML に変換され、#setInternalFormValue が呼ばれても既に正規化された値になるため、変更検知が走りません。

テストの追加

test/browser/fixtures/initial-value-renormalized.htmlinitial-value-sanitized.html を新規作成し、lexxy:change が初期ロード時に 0 件であることを検証するテストケースを events.test.js に追加しました。これにより、正規化やサニタイズが行われたシナリオでも期待通りイベントが抑制されることを自動的に保証します。

設計判断

この変更は 「初期値はサニタイズ済みのシリアライズ結果で内部状態を初期化する」 という設計方針を採用しました。サニタイズ設定はエディタ生成直後ではなく、実際にノードが生成される直前に実行することで、必ず _htmlImports が利用可能な状態で適用できます。さらに、内部フォーム値の設定ロジックは 「変更があった場合にだけ lexxy:change を発火」 という既存の振る舞いを保持しつつ、初期化時の余計な差分検出を防ぐ形で拡張されています。結果として、既存のカスタムリスナーやフォーム送信ロジックに対する後方互換性は維持され、イベントノイズの削減という明確な価値が得られました。

まとめ

PR #1095 は、エディタの初期化プロセスを見直し、サニタイズ済みのシリアライズ結果を内部フォーム値として設定することで、不要な lexxy:change イベントの発火を防止します。新しいヘルパーメソッドとコールバックの再構成により、ロジックがシンプルかつテスト可能となり、既存の機能や互換性に影響を与えずに信頼性が向上しました。

記事メタデータ

Generated by:
gpt-oss-120b for DiffDaily
LLM Trace:
d888ee16

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

品質レビュー結果

Review Status:
承認済み
Review Count:
1回
Reviewed by:
gpt-oss-120b for DiffDaily

Review Criteria:

記事構成 ✓ PASS

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

リード文、背景、技術的な変更、設計判断(任意)、まとめの全てが揃っており、総論→各論→結論の流れが明確です。

カスタムMarkdown構文 ⚠ WARNING

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

コードブロックのファイル名付きハイライトは正しい形式ですが、GitHubリンクは「[PR #1095](URL)」となっており、要件の「[#123](URL)」形式になっていません。

対象読者への適合性 ✓ PASS

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

専門的なエンジニア向けの記述で、余計な初心者向け説明はなく適切です。

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

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

各セクションは総論・各論・結論の構成になり、段落はトピックセンテンスで始まり1段落1トピック、長さも適切です。空行で区切られています。

Diff内容との照合 ✓ PASS

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

記事中のコードブロックは提供されたDiffと完全に一致しており、削除・追加の差分も正確に示されています。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

用語はPRおよびコードと一致しており、誤用は見当たりません。

説明の技術的正確性 ✓ PASS

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

変更の目的・効果・実装方法がPRの記載内容と整合しており、技術的に正確です。

事実の突合 ✓ PASS

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

すべての主張はPRやDiffで裏付けられており、根拠のない推測や矛盾はありません。

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

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

PR番号1095のみ使用され、正確です。その他の数値・固有名詞の誤りはありません。

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

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

記事タイトルはPRタイトルを適切に拡張しており、内容と一致しています。

外部知識の正確性 ✓ PASS

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

バージョンサポートやリリース日程等、PRに記載されていない外部情報は含まれていません。

時間表現の正確性 ✓ PASS

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

時間表現の歪曲はなく、PRの記述と合致しています。