`turbo-permanent`要素内のエディタをTurboキャッシュリセットから除外

basecamp/lexxy

Turboのページ遷移時に[data-turbo-permanent]コンテナ内のLexxyエディタが誤ってリセットされ、操作不能になるバグが修正されました。3行の条件分岐追加により、turbo:before-cacheハンドラは永続要素内のエディタをスキップするようになります。

背景

Turboの[data-turbo-permanent]要素はページスナップショットから除外され、ナビゲーション中もDOMに残り続けます。この仕組みにより、サイドバーや永続的なUIコンポーネントはページ遷移をまたいで状態を保持できます。

しかし、Lexxyの #handleTurboBeforeCache ハンドラはこの区別を考慮せず、ページ内のすべてのエディタに対して #reset() を呼び出していました。#reset()は内部のcontenteditabledivを取り除く処理であり、通常はconnectedCallbackが再実行されてエディタが再構築されます。[data-turbo-permanent]要素はDOMから切り離されも再接続もされないため、connectedCallbackが再び発火することがなく、エディタは永続的に操作不能な状態になります。

Basecampでは、この問題がサイドバーのpingテキスト入力欄として具体的に現れていました。カード編集モードに入り「Never Mind」をクリックしてページ遷移が発生すると、サイドバーのエディタが消失し、フルページリフレッシュなしには復元できなくなります。

技術的な変更

src/elements/editor.js#handleTurboBeforeCacheメソッドに、closest("[data-turbo-permanent]")を使った条件分岐が追加されました。

変更前:

#handleTurboBeforeCache = (event) => {
  this.#reset()
}

変更後:

#handleTurboBeforeCache = (event) => {
  if (!this.closest("[data-turbo-permanent]")) {
    this.#reset()
  }
}

closest() はDOM APIのメソッドで、要素自身およびその祖先ツリーを遡り、セレクタに一致する最初の要素を返します。エディタ自体またはその親要素のいずれかにdata-turbo-permanent属性があれば#reset()はスキップされます。これにより、[data-turbo-permanent]外のエディタは従来通りリセットされ、Turboのページスナップショットに正しく対応し続けます。

設計判断

closest()によるDOM探索 を採用したことで、エディタ自身が[data-turbo-permanent]属性を持たなくても、祖先要素に属性があれば適切に除外されます。これにより、エディタコンポーネント自体に永続性の情報を持たせる必要がなく、HTMLマークアップのみで制御できます。

また、エディタ側にTurboの永続要素の概念を明示的に登録する機構を設けるのではなく、既存のTurboのセマンティクス([data-turbo-permanent])をそのまま照合する設計になっています。Turboがスナップショットから除外する要素と、Lexxyがリセットをスキップする要素が同一の属性で定義されるため、両者の振る舞いが自然に一致します。

まとめ

この修正は、Turboの永続要素のライフサイクルとLexxyのリセット処理の前提が噛み合っていなかった問題を、Turbo自身のセマンティクスに準拠した3行の変更で解決しています。connectedCallbackが再実行されない要素に対してリセットを行わないという原則を明示することで、同種の問題が今後発生するリスクも低減されます。

記事メタデータ

Generated by:
Claude Sonnet 4.6 for DiffDaily
LLM Trace:
80eb641d

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

品質レビュー結果

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

Review Criteria:

記事構成 ✓ PASS

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

「総論→各論→結論」の3部構成が明確です。リード文、背景、技術的な変更、設計判断、まとめの各要素が適切に配置されており、記事全体の流れが非常に分かりやすいです。

カスタムMarkdown構文 ✓ PASS

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

ファイル名付きシンタックスハイライト(```javascript:src/elements/editor.js)やGitHubのPRリンク記法([PR #977](URL))が正しく使用されています。

対象読者への適合性 ✓ PASS

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

TurboやWeb Componentsに関する知識を持つエンジニアを対象としており、専門用語の使用や説明のレベルが適切です。過度な初心者向けの解説はありません。

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

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

各セクションが総論→各論の構成になっており、各段落はトピックセンテンスで始まっています。1段落1トピックの原則と適切な段落長が守られており、非常に高い可読性を実現しています。

Diff内容との照合 ✓ PASS

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

記事内のコードブロックは、提供されたDiff情報を正確に反映しています。ファイル名、変更前後のコード内容に誤りはありません。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

「turbo-permanent」「connectedCallback」「closest」などの技術用語が正確かつ適切な文脈で使用されています。

説明の技術的正確性 ✓ PASS

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

Turboの永続化の仕組み、`connectedCallback`が再実行されない問題、そして`closest`を用いた解決策の説明は、すべて技術的に正確で論理的です。

事実の突合 ✓ PASS

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

記事で述べられている問題の背景、具体例(Basecampのサイドバー)、解決策はすべてPRのDescriptionやDiffの内容で裏付けられており、ハルシネーション(捏造)は見られません。

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

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

PR番号(#977)や属性セレクタ([data-turbo-permanent])などの固有名詞・数値が正確に記載されています。

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

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

記事のタイトルは、PRのタイトル「Skip turbo:before-cache reset for editors inside turbo-permanent elements」の内容を的確に要約しており、主題のズレはありません。

外部知識の正確性 ✓ PASS

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

記事の内容は提供されたPR情報に限定されており、バージョンサポート状況やリリース日程など、PR外の知識を不適切に付加していません。

時間表現の正確性 ✓ PASS

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

「修正されました」といった過去形の表現が使われており、PRが完了した変更であることを正しく反映しています。時間表現の歪曲はありません。