Ordered List Numbering Offsets Preserved Across Load and Save

basecamp/lexxy

Ordered lists that start from a non‑1 index now retain their numbering when the editor loads, saves, and re‑opens the content. The fix expands the HTML sanitizers to allow the start attribute on <ol> elements, ensuring round‑trip fidelity.

背景

Lexical imports ordered lists using the start attribute of <ol> but ignores the value attribute on <li> during conversion, renumbering items sequentially from the list's start. Consequently, any list with an offset loses that offset after a load‑save cycle.

The sanitization layer further compounded the issue: while value on <li> was allow‑listed on both client and server, the start attribute on <ol> was stripped by DOMPurify when computing the form value and by Loofah when persisting through Action Text. As a result, the offset disappeared from the stored HTML, leading to the observed bug.

This mismatch between import‑export handling and attribute allow‑lists caused ordered lists such as

<ol start="2">
  <li value="2">Should be 2</li>
  <li value="3">Should be 3</li>
</ol>

to render as 1, 2 after a reload.

技術的な変更

The fix introduces start into the allowed attribute sets for both client‑side DOMPurify and server‑side Loofah, preserving the semantic attribute throughout the editor lifecycle.

On the server, lib/lexxy/engine.rb extends ActionText::ContentHelper.allowed_attributes:

default_allowed_attributes = Class.new.include(ActionText::ContentHelper).new.sanitizer_allowed_attributes
ActionText::ContentHelper.allowed_attributes = default_allowed_attributes + %w[ controls poster data-language style value start ]

On the client, the FormatEscapeExtension now returns an allow‑list that includes <ol> with the start attribute alongside the existing <li> value rule:

get allowedElements() {
  return [ { tag: "ol", attributes: [ "start" ] }, { tag: "li", attributes: [ "value" ] } ]
}

With these changes, the editor’s HTML round‑trip retains both start on <ol> and value on <li>, allowing Lexical to reconstruct the original numbering.

Automated tests verify the behavior. A browser test sets the editor value to a list with start="2" and asserts that the rendered HTML matches the input. A system test exercises the full edit‑save‑display‑re‑edit flow, confirming that the stored HTML includes ol[start='2'] and that the subsequent edit restores li[value] attributes.

設計判断

The chosen solution expands the existing allow‑list mechanism rather than introducing a new attribute handling pathway. By adding start to the permitted attribute sets, the change preserves backward compatibility and avoids altering Lexical’s import logic.

This approach also aligns client and server sanitizers, ensuring consistent HTML handling across the stack. The decision to keep the attribute allow‑listing centralized simplifies future maintenance: any additional semantic attributes for other nodes can follow the same pattern.

Overall, the fix demonstrates a minimal‑impact, surface‑level adjustment that resolves the bug without modifying core parsing or rendering code.

記事メタデータ

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

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

品質レビュー結果

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

Review Criteria:

記事構成 ⚠ WARNING

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

リード文はあるが、明確な「まとめ」セクションが欠如している。設計判断は含まれるが、結論としての要点整理が不足している。

カスタムMarkdown構文 ⚠ WARNING

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

コードブロックのファイル名付きハイライトは正しい形式だが、PRリンクの記法が「[PR #1112](URL)」となっており、要求された「[#1112](URL)」の形式に沿っていない。

対象読者への適合性 ✓ PASS

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

技術的な前提知識を持つエンジニア向けに記述されており、過度な基礎説明はない。

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

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

各セクションは総論→各論→結論の流れが保たれ、段落はトピックセンテンスで始まり、1段落1トピック、長さも適切。

Diff内容との照合 ✓ PASS

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

記事内のコードブロックは提供されたDiffと完全に一致しており、属性追加やファイル名も正確。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

「allow‑list」「DOMPurify」「Loofah」などの用語は正しく使用され、PR記述と整合している。

説明の技術的正確性 ✓ PASS

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

技術的な変更点や影響がPRの説明と一致しており、誤った主張は見られない。

事実の突合 ✓ PASS

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

記事の全ての主張はPRの説明、Diff、テスト追加情報に裏付けられており、捏造や推測はない。

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

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

PR番号 #1112、属性名、コード行など数値・固有名詞は正確。

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

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

記事タイトルはPRタイトルと意味的に一致しており、情報のずれはない。

外部知識の正確性 ✓ PASS

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

バージョンサポートやリリース日程などPRに無関係な外部情報は含まれていない。

時間表現の正確性 ✓ PASS

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

時間表現の不整合はなく、PRの記述と合致している。