プレビュー生成のデファード対応:preview_status_url によるポーリングオプション

basecamp/lexxy

Lexxy はプレビュー可能な PDF や動画のアップロード後、プレビュー画像の URL を <img> に埋め込み、キャッシュバスター付きでポーリングし続けていました。この変更は、プレビュー生成が遅延するバックエンドでも軽量に状態確認できる preview_status_url オプションを導入し、従来のポーリング方式を置き換えます。

背景

Lexxy の従来実装では、サーバーがプレースホルダー画像を返す前提でプレビュー URL を繰り返し取得し、画像サイズがヒューリスティックな閾値を超えるまでキャッシュバスター付きリクエストを送っていました。結果として、プレビューが生成中でもリクエストスレッド上で画像生成が走り、負荷が跳ね返ってくるケースがありました。

この設計は、Active Storage が「リクエスト時にプレビューを生成」するデフォルト動作と合致しませんでした。バックエンドが遅延生成(バックグラウンドジョブ等)を行う場合、毎回のポーリングが実際の生成処理をトリガーしてしまい、エディタ側の UX が不必要に重くなります。

そこで、アップロードレスポンスに preview_status_url を任意で含めることで、プレビューの生成状態を軽量に監視できる新しいパスを設けました。このオプションはオプトイン方式であるため、既存のホストは変更不要で従来通り動作します。

技術的な変更

ActionTextAttachmentNode に対して次の定数と属性が追加されました:INITIAL_PREVIEW_POLL_DELAY_MS, MAX_PREVIEW_POLL_DELAY_MS, MAX_PREVIEW_POLL_ATTEMPTS、そして previewStatusUrl プロパティです。これにより、ノードはステータス URL の有無で二分岐し、ポーリングまたは画像プリロードを選択します。

@@
-  constructor({ tagName, sgid, src, previewSrc, previewable, pendingPreview, altText, caption, contentType, fileName, fileSize, width, height, uploadError }, key) {
+  constructor({ tagName, sgid, src, previewSrc, previewable, previewStatusUrl, pendingPreview, altText, caption, contentType, fileName, fileSize, width, height, uploadError }, key) {
@@
-    previewable: this.previewable,
+    previewable: this.previewable,
+    previewStatusUrl: this.previewStatusUrl,

さらに #pollForPreview メソッドが拡張され、this.previewStatusUrl が存在すれば #waitForPreviewByPollingStatus を呼び、無ければ #waitForPreviewByPreloadingImage を呼び出すロジックが実装されました。ステータスエンドポイントへのポーリングは 2xx が処理中、それ以外が完了とみなすシンプルな契約に基づき、指数バックオフと最大 20 回のリトライを行います。

テストスイートも同様に拡張され、preview_status_url.test.js で両パスが網羅的に検証されています。モックレイヤー active_storage_mock.jsincludePreviewStatusUrlpreviewStatusInitiallyProcessing フラグを受け取り、ステータス URL およびプレビュー URL のリクエスト記録を可能にしています。

設計判断

preview_status_url は任意フィールドとしてレスポンスに追加された点が重要です。ホスト側がバックグラウンドでプレビューを生成できる場合にだけこのエンドポイントを提供し、Lexxy は自動的に低負荷なステータスチェックへ切り替えます。逆にフィールドが無い場合は、従来通りプレビュー URL の直接ロードを行うため、既存の Active Storage 互換性がそのまま保たれます。

このオプトイン方式は、後方互換性 を最大化しつつ新機能を導入できる典型的な設計パターンです。コード上の変更は主にノードのコンストラクタとポーリングロジックの追加に留まり、既存 API(previewablesrc など)への影響はありません。また、バージョン番号とドキュメントの更新(docs/attachments.md)だけで機能が利用可能になるため、デプロイリスクが低減します。

まとめ

この PR は、プレビュー生成が遅延する環境でもエディタ側の負荷を抑える preview_status_url オプションを導入し、既存のポーリングロジックを置き換えました。オプトイン方式により従来の動作はそのまま維持され、バックエンドが余計なリクエストでプレビュー生成を起動することが防止されます。結果として、Lexxy はよりスケーラブルでバックエンド多様性に対応したプレビュー機構を提供します。

記事メタデータ

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

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

品質レビュー結果

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

Review Criteria:

記事構成 ✓ PASS

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

リード文→背景→技術的な変更→設計判断→まとめの3部構成が明確に存在し、各要素が揃っている。

カスタムMarkdown構文 ⚠ WARNING

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

コードブロックのファイル名付きシンタックスハイライトは正しい形式。PRリンクは `[PR #1074](URL)` となっており、期待される `[#1074](URL)` の形式と完全には一致していない。

対象読者への適合性 ✓ PASS

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

専門的なエンジニア向けの記述で、過度な初心者向け解説はない。

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

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

各セクションは総論パラグラフが存在するが、各論の後に明確な結論パラグラフが欠けている。全体の可読性は確保されているため警告レベルとした。

Diff内容との照合 ✓ PASS

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

記事中のコードブロックは提供されたDiffに含まれる変更と一致している。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

使用されている技術用語はPRやDiffで示されたものと合致しており、誤用はない。

説明の技術的正確性 ✓ PASS

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

技術的説明はDiffとPRの内容に基づいており、矛盾や誤りは見られない。

事実の突合 ✓ PASS

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

記事の主張はすべてPRの要旨、Diff、またはコードと照合でき、捏造や推測はない。

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

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

定数 MAX_PREVIEW_POLL_ATTEMPTS の数値 20 はDiffで確認でき、他の数値に誤りはない。

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

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

記事タイトルはPRタイトルの内容を日本語で正確に要約している。

外部知識の正確性 ✓ PASS

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

記事に外部知識(LTS、リリース日程等)は含まれておらず、PR情報のみで完結している。

時間表現の正確性 ✓ PASS

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

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