# 通知設計書 16-デプロイプレビュー読み込み失敗通知

## 概要

本ドキュメントは、Decap CMS のデプロイプレビュー機能において、プレビュー情報の読み込みが失敗した場合に表示されるエラートースト通知の設計を記述する。

### 本通知の処理概要

デプロイプレビュー読み込み失敗通知は、エントリに対応するデプロイプレビューURL（公開前のプレビューサイトURL）をバックエンドから取得しようとした際に、取得処理が例外をスローした場合にエラー情報をフィードバックするトースト通知である。

**業務上の目的・背景**：デプロイプレビューは、エントリの公開前にコンテンツの見た目を確認するための重要な機能である。Netlify や Vercel 等のホスティングサービスと連携し、プルリクエストベースのプレビューURLを提供する。プレビューの読み込みに失敗した場合、ユーザーがコンテンツの公開前確認を行えなくなるため、失敗を通知して状況を把握させる必要がある。

**通知の送信タイミング**：`loadDeployPreview` アクション内の catch ブロックで、`backend.getDeploy()` または `backend.getDeployPreview()` の呼び出しが例外をスローした時点で送信される。

**通知の受信者**：デプロイプレビューの読み込みが発生したページを閲覧している現在のログインユーザー。クライアントサイドのアプリ内通知であるため、当該ブラウザセッションのユーザーのみが受信する。

**通知内容の概要**：i18nキー `ui.toast.onFailToLoadDeployPreview` に対応するローカライズされたエラーメッセージが表示される。英語では "Failed to load preview: %{details}"、日本語では "プレビューの読み込みに失敗しました。%{details}" となる。`%{details}` には `error.message` が埋め込まれる。

**期待されるアクション**：ユーザーはエラー内容を確認し、ページのリロード、デプロイサービスのステータス確認、バックエンドの接続状況確認を行うことが期待される。プレビュー読み込み失敗はエントリの編集・公開処理自体には影響しないため、プレビュー確認なしで公開を続行することも可能である。

## 通知種別

アプリ内トースト通知（react-toastify ベース）

## 送信仕様

### 基本情報

| 項目 | 内容 |
|-----|------|
| 送信方式 | 同期（Redux dispatch） |
| 優先度 | 中 |
| リトライ | なし（通知自体のリトライはなし。デプロイプレビューの再読込はページリロードで可能） |

### 送信先決定ロジック

操作を実行した現在のブラウザセッションユーザーに対して、クライアントサイドで直接トースト通知を表示する。Redux ストアの `notifications` ステートに通知が追加され、`Notifications` コンポーネントが react-toastify を通じてUIに描画する。

## 通知テンプレート

### メール通知の場合

該当なし（本通知はアプリ内トースト通知のみ）

### 本文テンプレート

```
Failed to load preview: {details}
```

日本語の場合：
```
プレビューの読み込みに失敗しました。{details}
```

### 添付ファイル

該当なし

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| details | エラーメッセージ文字列 | `backend.getDeploy()` / `backend.getDeployPreview()` がスローした Error オブジェクトの `message` プロパティ | Yes |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| 画面表示 | エントリエディタやプレビューパネルの表示時 | `backend.getDeploy()` または `backend.getDeployPreview()` が例外をスローした場合 | `loadDeployPreview` thunkアクション内の catch ブロック（行85-98）で発火 |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| 既にフェッチ中の場合 | `deployState.isFetching` が true の場合、重複リクエストは送信されない（行67-69） |
| デプロイ情報が正常に取得できた場合 | 成功時は `deployPreviewLoaded` が dispatch され、本エラー通知は表示されない |
| デプロイ情報が null/undefined の場合 | `deploy` が falsy の場合は `deployPreviewError` のみが dispatch され、通知は表示されない（行84） |

## 処理フロー

### 送信フロー

```mermaid
flowchart TD
    A[エントリエディタ/プレビューパネル表示] --> B[loadDeployPreview thunk dispatch]
    B --> C{isFetching?}
    C -->|Yes| D[早期リターン]
    C -->|No| E[deployPreviewLoading dispatch]
    E --> F{published?}
    F -->|Yes| G[backend.getDeploy 呼び出し]
    F -->|No| H[backend.getDeployPreview 呼び出し]
    G --> I{結果}
    H --> I
    I -->|deploy取得成功| J[deployPreviewLoaded dispatch]
    I -->|deploy null| K[deployPreviewError dispatch]
    I -->|例外スロー| L[catch ブロック]
    L --> M[addNotification dispatch - onFailToLoadDeployPreview]
    M --> N[deployPreviewError dispatch]
    N --> O[トースト表示 8秒後自動消去]
    J --> P[終了]
    K --> P
    D --> P
    O --> P
```

## データベース参照・更新仕様

### 参照テーブル一覧

該当なし（クライアントサイドのみの処理）

### 更新テーブル一覧

| テーブル名 | 操作 | 概要 |
|-----------|------|------|
| Redux Store: notifications | INSERT | 通知オブジェクトを notifications 配列に追加 |
| Redux Store: deploys | UPDATE | DEPLOY_PREVIEW_FAILURE によりデプロイプレビューリクエスト状態をエラーに設定 |

#### Redux Store 更新詳細

| 操作 | 項目 | 更新値 | 備考 |
|-----|------|-------|------|
| INSERT | notifications.notifications[] | `{ id: uuid(), message: { details: error.message, key: 'ui.toast.onFailToLoadDeployPreview' }, type: 'error', dismissAfter: 8000 }` | details に error.message を使用 |

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 対処方法 |
|----------|---------|---------|
| バックエンドAPI通信エラー | `backend.getDeploy()` / `backend.getDeployPreview()` の通信エラー | エラー詳細をトースト通知で表示し、`deployPreviewError` を dispatch |
| デプロイサービス障害 | 外部デプロイサービス（Netlify等）がダウンしている場合 | 同上 |

### リトライ仕様

| 項目 | 内容 |
|-----|------|
| リトライ回数 | 0回（通知レベルでは自動リトライなし） |
| リトライ間隔 | 該当なし |
| リトライ対象エラー | 該当なし |

注意：`loadDeployPreview` 自体は `opts` パラメータで `maxAttempts` と `interval` を受け取ることができ、バックエンド側でリトライロジックが実装されている可能性がある（行58）。ただし、最終的に例外がスローされた場合に本通知が発火する。

## 配信設定

### レート制限

| 項目 | 内容 |
|-----|------|
| 1分あたり上限 | なし |
| 1日あたり上限 | なし |

### 配信時間帯

制限なし

## セキュリティ考慮事項

- `error.message` がトースト通知に表示されるため、バックエンドの内部情報が含まれる可能性がある。
- クライアントサイドのトースト通知であり、操作ユーザー本人のブラウザにのみ表示されるため、リスクは限定的である。

## 備考

- 本通知の `details` には `error.message`（Error オブジェクトの message プロパティ）が使用される。他の多くの通知では error オブジェクト自体が渡されるのに対し、本通知では明示的に `error.message` が抽出されている点が異なる。
- 公開済みエントリの場合は `backend.getDeploy()` が、未公開エントリの場合は `backend.getDeployPreview()` が呼ばれる。いずれの失敗でも同じ通知が表示される。
- `console.error(error)` でコンソールにもエラーが出力される（行86）。
- `dismissAfter: 8000` が設定されており、8秒後に自動消去される。
- 重複フェッチ防止のガードが実装されている（`deployState.isFetching` チェック、行67-69）。

---

## コードリーディングガイド

本通知を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | notifications.ts (actions) | `packages/decap-cms-core/src/actions/notifications.ts` | `NotificationPayload` と `NotificationMessage` の型定義を理解する |
| 1-2 | deploys.ts | `packages/decap-cms-core/src/actions/deploys.ts` | `DeploysAction` 型（行102-104）と、`deployPreviewLoading` / `deployPreviewLoaded` / `deployPreviewError` の各アクション型を理解する |

**読解のコツ**: `details` が `error.message` として渡されるパターンに注意。他の通知では error オブジェクトそのものが渡されることが多い。

#### Step 2: エントリーポイントを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | deploys.ts | `packages/decap-cms-core/src/actions/deploys.ts` | `loadDeployPreview` 関数（行53-100）がデプロイプレビュー読み込みのエントリーポイント |

**主要処理フロー**:
1. **行61-62**: バックエンドとコレクション名の取得
2. **行66-69**: フェッチ中の重複チェック（`isFetching` ガード）
3. **行71**: `deployPreviewLoading` を dispatch
4. **行78-80**: 公開済みなら `backend.getDeploy()`、未公開なら `backend.getDeployPreview()` を呼び出し
5. **行81-84**: 結果の処理（deploy 存在時は loaded、null時は error）
6. **行85-98**: catch ブロック（**本通知の発火箇所**）
7. **行87-96**: `addNotification` を dispatch
8. **行97**: `deployPreviewError` を dispatch

#### Step 3: 通知ディスパッチの仕組みを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | notifications.ts (actions) | `packages/decap-cms-core/src/actions/notifications.ts` | `addNotification` 関数（行18-19） |
| 3-2 | notifications.ts (reducers) | `packages/decap-cms-core/src/reducers/notifications.ts` | `NOTIFICATION_SEND` ケース（行40-47） |

#### Step 4: UIレンダリングを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | Notifications.tsx | `packages/decap-cms-core/src/components/UI/Notifications.tsx` | 行37で `t(notification.message.key, { ...notification.message })` により翻訳される。`details` が `%{details}` に展開される |

#### Step 5: i18nメッセージを確認する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | index.js (en) | `packages/decap-cms-locales/src/en/index.js` | 行270: `onFailToLoadDeployPreview: 'Failed to load preview: %{details}'` |
| 5-2 | index.js (ja) | `packages/decap-cms-locales/src/ja/index.js` | 行257: `onFailToLoadDeployPreview: 'プレビューの読み込みに失敗しました。%{details}'` |

### プログラム呼び出し階層図

```
loadDeployPreview (deploys.ts:53)
    |
    +-- selectDeployPreview (reducers.ts)
    |
    +-- deployPreviewLoading (deploys.ts:13)
    |
    +-- [公開済み] backend.getDeploy (collection, slug, entry)
    |   [未公開] backend.getDeployPreview (collection, slug, entry, opts)
    |       |
    |       +-- [成功・deploy存在] deployPreviewLoaded (deploys.ts:23)
    |       +-- [成功・deploy null] deployPreviewError (deploys.ts:40)
    |       +-- [失敗] catch ブロック (deploys.ts:85)
    |               |
    |               +-- console.error(error)
    |               +-- addNotification (notifications.ts:18)
    |               |       |
    |               |       +-- NOTIFICATION_SEND → reducer
    |               |               |
    |               |               +-- Notifications.tsx → toast()
    |               |
    |               +-- deployPreviewError (deploys.ts:40)
```

### データフロー図

```
[入力]                           [処理]                              [出力]

エントリ表示時の自動読込 ──> loadDeployPreview thunk ──────> Redux dispatch
  (collection, slug,             |                                    |
   entry, published)     isFetching ガードチェック                     |
                                 |                                    |
                         backend.getDeploy /               NOTIFICATION_SEND
                         backend.getDeployPreview                     |
                                 |                            reducer: notifications
                         [例外スロー]                                 |
                                 |                            Notifications コンポーネント
                         addNotification({                           |
                           message: {                          react-toastify toast()
                             details: error.message,                 |
                             key: 'ui.toast.                  トースト表示（8秒後自動消去）
                               onFailToLoadDeployPreview'
                           },
                           type: 'error',
                           dismissAfter: 8000
                         })
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| deploys.ts | `packages/decap-cms-core/src/actions/deploys.ts` | ソース | デプロイプレビュー読み込みのthunkアクション。失敗時に通知を dispatch する |
| notifications.ts (actions) | `packages/decap-cms-core/src/actions/notifications.ts` | ソース | `addNotification` アクションクリエーター |
| notifications.ts (reducers) | `packages/decap-cms-core/src/reducers/notifications.ts` | ソース | 通知ステートの管理リデューサー |
| Notifications.tsx | `packages/decap-cms-core/src/components/UI/Notifications.tsx` | ソース | トースト通知のUIコンポーネント |
| index.js (en) | `packages/decap-cms-locales/src/en/index.js` | i18n | 英語翻訳定義 |
| index.js (ja) | `packages/decap-cms-locales/src/ja/index.js` | i18n | 日本語翻訳定義 |
