SandboxページをTurbo Frame化してプリセット切り替えを実装

basecamp/lexxy

LexxyのSandboxページが、Turbo Frameを使ったプリセット切り替え機能に刷新されました。これにより、ページ遷移なしでエディタの初期コンテンツを切り替えられるようになり、開発中の動作確認がより効率的になります。

背景

従来のSandboxページでは、異なる初期コンテンツ(デフォルト、テーブル、コードブロックなど)をロードするために、Stimulusコントローラがfetchでパーシャルを取得し、エディタの値を書き換える実装になっていました。この方式では、各プリセットへの直リンクが作成できず、ブラウザ履歴にも反映されないという制約がありました。

本PRは、プリセット切り替えをTurbo Frameベースに再実装し、#688で報告されている問題への暫定的な回避策を含んでいます。

技術的な変更

変更前:

async loadContent({ params: { partial } }) {
  const response = await fetch(`/demo_contents/${partial}`)
  const html = await response.text()
  this.editorTarget.value = html.trim()
  await new Promise(resolve => requestAnimationFrame(resolve))
  this.refresh()
}
<div class="editor-value-buttons">
  <button data-action="click->lexxy-output#loadContent" data-lexxy-output-partial-param="default">Default</button>
  <button data-action="click->lexxy-output#loadContent" data-lexxy-output-partial-param="tables">Tables</button>
  <!-- 他のボタン -->
</div>

変更後:

get "sandbox/:template", to: "sandbox#show"
ALLOWED_TEMPLATES = %w[default tables code lists highlights images empty]

def show
  @template = if params[:template].presence_in ALLOWED_TEMPLATES
    params[:template]
  else
    "default"
  end
end
<main data-controller="lexxy-output history">
  <section class="input">
    <div class="editor-value-buttons">
      <a href="/sandbox/default" class="button" data-turbo-frame="editor-frame">Default</a>
      <a href="/sandbox/tables" class="button" data-turbo-frame="editor-frame">Tables</a>
      <!-- 他のリンク -->
    </div>
    <turbo-frame id="editor-frame" src="/sandbox/<%= @template %>">
      <%= render @template %>
    </turbo-frame>
  </section>
</main>

Stimulus Controller経由のfetch処理が削除され、代わりにルーティングとTurbo Frameによるプリセット切り替えに変更されています。コントローラではALLOWED_TEMPLATESのホワイトリストでセキュリティを確保し、不正なテンプレート名が指定された場合はデフォルトにフォールバックします。

Turbo Frame内での履歴管理

Turbo Frameによるナビゲーションでは、デフォルトではブラウザ履歴が更新されません。この問題に対処するため、履歴管理用のStimulus Controllerが追加されています。

import { Controller } from "@hotwired/stimulus"

export default class HistoryController extends Controller {
  pushFromFrameLoad(event) {
    const frame = event.target
    const url = frame.src
    window.history.pushState({}, "", url)
  }
}

turbo:frame-loadイベントをフックして、フレームのURL変更時にhistory.pushState()で履歴エントリを追加しています。これにより、ブラウザの戻る/進むボタンでプリセット間を移動できるようになります。PR本文では、この実装が#688の解決までの「work-around」として位置づけられています。

プリセットの拡充

新たにimagesプリセットが追加され、画像の表示パターン(単一画像、ギャラリー、連続配置)をテストできるようになりました。

<h3>Single image</h3>
<img src="<%= asset_path('shape-2.svg') %>" alt="Wave pattern with gradient">
<p>Hello world</p>

<h3>Image gallery</h3>
<div>
  <img src="<%= asset_path('shape-1.svg') %>" alt="Geometric pattern with circles">
  <img src="<%= asset_path('shape-2.svg') %>" alt="Wave pattern with gradient">
  <img src="<%= asset_path('shape-3.svg') %>" alt="Rotated squares pattern">
</div>

<h3>Consecutive images</h3>
<img src="<%= asset_path('shape-1.svg') %>" alt="Geometric pattern with circles">
<img src="<%= asset_path('shape-2.svg') %>" alt="Wave pattern with gradient">
<img src="<%= asset_path('shape-3.svg') %>" alt="Rotated squares pattern">

3つのSVG画像ファイル(shape-1.svgshape-2.svgshape-3.svg)がtest/dummy/app/assets/images/に追加され、グラデーションや幾何学模様を含むテスト用アセットとして利用できます。

設計判断

Stimulusベースの動的ロードからルーティングベースの実装への移行は、URLの直接共有可能性とブラウザネイティブなナビゲーション体験を優先した判断です。各プリセットが独立したURLを持つことで、特定の状態を再現しやすくなり、開発時の動作確認効率が向上します。

一方で、Turbo Frame内での履歴管理の実装は、PR本文で明示的に「work-around」として扱われています。history_controller.jsの追加は、#688が解決されるまでの暫定的な対応として機能します。

まとめ

本PRは、Sandboxページの実装をTurbo Frameベースに再構築し、プリセット切り替えをURLルーティングで実現しています。Stimulusによる動的コンテンツロードを排除することで、各プリセットへの直リンクとブラウザ履歴の統合を可能にしました。開発体験の向上を実現しつつ、既知の制約への実用的な回避策を提供しています。

記事メタデータ

Generated by:
Claude Sonnet 4.5 for DiffDaily

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

品質レビュー結果

Review Status:
リトライ後承認
Review Count:
2回 (改善を経て承認)
Reviewed by:
Gemini 2.5 Pro for DiffDaily

Review Criteria:

記事構成 ✓ PASS

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

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

カスタムMarkdown構文 ✓ PASS

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

ファイル名付きシンタックスハイライトの形式、およびGitHubのIssue/PRへのリンク記法はすべて正しく使用されています。

対象読者への適合性 ✓ PASS

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

Turbo FrameやStimulusといった技術要素を前提とした説明になっており、専門知識を持つエンジニアという対象読者に適合した内容です。

パラグラフ・ライティング ⚠ WARNING

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

ほとんどのセクションやパラグラフは適切に構成されていますが、「技術的な変更」セクションにセクションの要旨を説明する総論パラグラフが欠けています。

Diff内容との照合 ✓ PASS

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

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

技術用語の正確性 ✓ PASS

技術用語の正確な使用

Turbo Frame, Stimulus, history.pushState, turbo:frame-loadなど、使用されている技術用語は文脈に即しており、正確です。

説明の技術的正確性 ✓ PASS

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

StimulusによるfetchからTurbo Frameへの移行、履歴管理のためのwork-aroundなど、技術的な変更に関する説明は論理的で正確です。

事実の突合 ✓ PASS

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

記事内のすべての主張は、PRのDescriptionやDiff内のコード変更によって裏付けられており、ハルシネーションは見られません。「設計判断」の記述もPRの変更内容から導かれる妥当な解釈です。

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

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

PR番号(#723)やIssue番号(#688)などの数値や固有名詞は、PR情報と正確に一致しています。

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

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

記事のタイトルはPRの主題である「Sandboxの改善」を具体的に表現しており、内容と一致しています。

外部知識の正確性 ✓ PASS

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

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

時間表現の正確性 ✓ PASS

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

「#688が解決されるまでの暫定的な対応」といった時間的な関係性の表現は、PR Descriptionの記述と正確に一致しています。