# 通知設計書 2-エントリ読み込み失敗通知

## 概要

本ドキュメントは、Decap CMSにおけるエントリまたはエントリ一覧の読み込みに失敗した場合に表示されるエラートースト通知の設計仕様を定義する。コンテンツ管理画面でエントリデータの取得中にエラーが発生した際、ユーザーに失敗を通知する仕組みの詳細を記載する。

### 本通知の処理概要

本通知は、Decap CMSのエントリ管理機能においてエントリの読み込みが失敗した場合に発火するエラートースト通知である。単一エントリの読み込み（`loadEntry`）、エントリ一覧の読み込み（`loadEntries`）、ページネーションによるカーソルトラバース（`traverseCollectionCursor`）、および編集ワークフローの未公開エントリ読み込み（`loadUnpublishedEntry`, `loadUnpublishedEntries`）の各関数内のcatchブロックで同一のi18nキー `ui.toast.onFailToLoadEntries` を使用して通知が発行される。

**業務上の目的・背景**：CMSのコンテンツ編集者がエントリを閲覧・編集しようとした際にデータ取得に失敗した場合、エラーの発生をユーザーに即座に伝え、ページのリロードや管理者への報告などの次のアクションを促す。バックエンドの一時的な障害やネットワーク問題への気づきを与える。

**通知の送信タイミング**：以下のいずれかの操作でバックエンドへのAPI呼び出しが失敗（Promise reject）した時点で通知が発行される。(1) 個別エントリの読み込み、(2) エントリ一覧の読み込み、(3) ページネーション操作、(4) 未公開エントリの読み込み、(5) 未公開エントリ一覧の読み込み。

**通知の受信者**：操作を行った当該ユーザー本人。ブラウザ画面上のトースト通知として表示されるため、操作中のユーザーのみが受信者となる。

**通知内容の概要**：i18nキー `ui.toast.onFailToLoadEntries` に基づくメッセージが表示される。英語ロケールでは `Failed to load entry: %{details}` として、バックエンドから返されたエラーの詳細がメッセージに含まれる。

**期待されるアクション**：ユーザーはエラーメッセージを確認し、ページの再読み込み、別のエントリの選択、またはシステム管理者への障害報告を行うことが期待される。

## 通知種別

アプリ内通知（トースト通知）

react-toastify ライブラリを使用したブラウザ内トースト通知。画面右上（`top-right`）に `colored` テーマで表示される。

## 送信仕様

### 基本情報

| 項目 | 内容 |
|-----|------|
| 送信方式 | 同期（Redux dispatch → react-toastify） |
| 優先度 | 高（コンテンツ閲覧・編集に直結するエラー） |
| リトライ | なし（UIトースト通知のため再送不要） |

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

エントリ読み込み操作を実行したユーザーのブラウザセッションに対して直接表示する。Redux storeの `notifications` ステートに追加されたエントリが `Notifications` コンポーネントによってレンダリングされる。

## 通知テンプレート

### メール通知の場合

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

### 本文テンプレート

```
Failed to load entry: %{details}
```

i18nキー `ui.toast.onFailToLoadEntries` のテンプレート。`%{details}` にはバックエンドから返されたエラーオブジェクトの情報が挿入される。

### 添付ファイル

該当なし

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| details | エラーの詳細情報 | 各関数のcatchブロックで捕捉されたerrorオブジェクト（`error.message` またはerrorオブジェクト自体） | Yes |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| 画面操作 | 個別エントリ読み込み | `loadEntry` 内で `tryLoadEntry` が例外をスロー | エントリ編集画面への遷移時（entries.ts 539-551行目） |
| 画面操作 | エントリ一覧読み込み | `loadEntries` 内で provider.listEntries/listAllEntries が失敗 | コレクション一覧画面表示時（entries.ts 644-656行目） |
| 画面操作 | ページネーション操作 | `traverseCollectionCursor` 内で `traverseCursor` が例外をスロー | 一覧画面でのページ送り操作時（entries.ts 695-708行目） |
| 画面操作 | 未公開エントリ読み込み | `loadUnpublishedEntry` 内で backend.unpublishedEntry が失敗（ワークフローエラー以外） | 未公開エントリ編集時（editorialWorkflow.ts 277-286行目） |
| 画面操作 | 未公開エントリ一覧読み込み | `loadUnpublishedEntries` 内で backend.unpublishedEntries が失敗 | ワークフローダッシュボード表示時（editorialWorkflow.ts 306-318行目） |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| エントリ読み込み成功時 | 各APIが正常にresolveした場合は通知を発行しない |
| EDITORIAL_WORKFLOW_ERRORで notUnderEditorialWorkflow がtrue | 未公開エントリが編集ワークフロー配下でない場合は通常のloadEntryにリダイレクトし、この通知は発行しない（editorialWorkflow.ts 273-276行目） |

## 処理フロー

### 送信フロー

```mermaid
flowchart TD
    A[エントリ読み込み操作] --> B{読み込み関数}
    B -->|loadEntry| C[backend.getEntry]
    B -->|loadEntries| D[provider.listEntries/listAllEntries]
    B -->|traverseCollectionCursor| E[backend.traverseCursor]
    B -->|loadUnpublishedEntry| F[backend.unpublishedEntry]
    B -->|loadUnpublishedEntries| G[backend.unpublishedEntries]
    C -->|失敗| H[catch ブロック実行]
    D -->|失敗| H
    E -->|失敗| H
    F -->|失敗| I{エラー種別判定}
    G -->|失敗| H
    I -->|EDITORIAL_WORKFLOW_ERROR| J[通常エントリ読み込みにリダイレクト]
    I -->|その他| H
    H --> K[dispatch addNotification - エラートースト表示]
    K --> L[dispatch entryLoadError/entriesFailed 等]
    L --> M[Notifications コンポーネントがトースト描画]
    M --> N[8秒後に自動消去]
    N --> O[終了]
    J --> O
```

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

### 参照テーブル一覧

該当なし（本通知はクライアントサイドのRedux storeのみを使用）

### 更新テーブル一覧

該当なし

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 対処方法 |
|----------|---------|---------|
| バックエンドAPI失敗 | エントリデータの取得APIが500エラー等を返す | エラートースト通知を表示し、Redux stateにエラー状態を記録 |
| ネットワークエラー | バックエンドへの接続が失敗 | 同上 |
| 認証期限切れ | バックエンドが401を返す | 同上（別途セッション切断通知で対応される場合もある） |

### リトライ仕様

| 項目 | 内容 |
|-----|------|
| リトライ回数 | なし |
| リトライ間隔 | 該当なし |
| リトライ対象エラー | 該当なし（ユーザーが画面をリロードして再試行する想定） |

## 配信設定

### レート制限

| 項目 | 内容 |
|-----|------|
| 1分あたり上限 | なし（UI通知のため制限なし） |
| 1日あたり上限 | なし |

### 配信時間帯

制限なし（エントリ読み込み操作が失敗したタイミングで即座に表示）

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

- エラーオブジェクト（`details`）が画面に表示されるため、バックエンドが内部情報を含むエラーを返さないよう注意が必要
- `loadEntries` と `traverseCollectionCursor` ではエラーオブジェクトがそのまま（文字列変換なしで）detailsに渡されている点に留意

## 備考

- 同一のi18nキー `ui.toast.onFailToLoadEntries` が5つの異なる関数から呼び出されるため、通知メッセージだけではどの操作で失敗したかの特定が困難な場合がある
- `loadUnpublishedEntry` では `EDITORIAL_WORKFLOW_ERROR` の `notUnderEditorialWorkflow` プロパティをチェックし、編集ワークフロー配下でないエントリの場合はトースト通知を表示せず通常の `loadEntry` にフォールバックする特殊処理がある（editorialWorkflow.ts 273-276行目）
- `loadEntries` と `traverseCollectionCursor` ではトースト通知表示後に `Promise.reject` を返す

---

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

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

### 推奨読解順序

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

通知ペイロードの構造を把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | notifications.ts (actions) | `packages/decap-cms-core/src/actions/notifications.ts` | `NotificationPayload` インターフェース（8-12行目）。`message`にi18nキーとdetailsを含む `NotificationMessage` が渡される。 |

**読解のコツ**: `details` フィールドの型は `unknown` であり、エラーオブジェクトがそのまま渡されるケースと `error.message` が渡されるケースの両方がある。

#### Step 2: エントリーポイントを理解する（entries.ts）

主要な呼び出し元を特定する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | entries.ts | `packages/decap-cms-core/src/actions/entries.ts` | `loadEntry`（530-553行目）、`loadEntries`（575-658行目）、`traverseCollectionCursor`（667-710行目）の3つの関数内のcatchブロックに注目。 |

**主要処理フロー**:
1. **540-551行目**: `loadEntry` - 個別エントリ読み込み失敗時の通知。`error.message` がdetailsに渡される。
2. **645-655行目**: `loadEntries` - エントリ一覧読み込み失敗時の通知。`err` オブジェクトがそのままdetailsに渡される。
3. **697-707行目**: `traverseCollectionCursor` - ページネーション失敗時の通知。`err` オブジェクトがそのままdetailsに渡される。

#### Step 3: エントリーポイントを理解する（editorialWorkflow.ts）

編集ワークフロー側の呼び出し元を特定する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | editorialWorkflow.ts | `packages/decap-cms-core/src/actions/editorialWorkflow.ts` | `loadUnpublishedEntry`（240-290行目）、`loadUnpublishedEntries`（292-321行目）の2つの関数内のcatchブロックに注目。 |

**主要処理フロー**:
- **272-288行目**: `loadUnpublishedEntry` catchブロック。`EDITORIAL_WORKFLOW_ERROR` かつ `notUnderEditorialWorkflow` の場合はリダイレクト、それ以外はトースト通知。
- **306-318行目**: `loadUnpublishedEntries` catchブロック。エラーオブジェクトがdetailsに渡される。

#### Step 4: i18nメッセージテンプレートを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | index.js (en) | `packages/decap-cms-locales/src/en/index.js` | 269行目: `onFailToLoadEntries: 'Failed to load entry: %{details}'` |

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

```
[entries.ts]
loadEntry (entries.ts:530)
    |
    +-- tryLoadEntry → backend.getEntry
    |       +-- [失敗時] dispatch(addNotification({key:'ui.toast.onFailToLoadEntries'}))
    |       +-- dispatch(entryLoadError)
    |
loadEntries (entries.ts:575)
    |
    +-- provider.listEntries / provider.listAllEntries
    |       +-- [失敗時] dispatch(addNotification({key:'ui.toast.onFailToLoadEntries'}))
    |       +-- dispatch(entriesFailed)
    |
traverseCollectionCursor (entries.ts:667)
    |
    +-- traverseCursor → backend.traverseCursor
            +-- [失敗時] dispatch(addNotification({key:'ui.toast.onFailToLoadEntries'}))
            +-- dispatch(entriesFailed)

[editorialWorkflow.ts]
loadUnpublishedEntry (editorialWorkflow.ts:240)
    |
    +-- backend.unpublishedEntry
    |       +-- [EDITORIAL_WORKFLOW_ERROR] unpublishedEntryRedirected → loadEntry
    |       +-- [その他の失敗] dispatch(addNotification({key:'ui.toast.onFailToLoadEntries'}))
    |
loadUnpublishedEntries (editorialWorkflow.ts:292)
    |
    +-- backend.unpublishedEntries
            +-- [失敗時] dispatch(addNotification({key:'ui.toast.onFailToLoadEntries'}))
            +-- dispatch(unpublishedEntriesFailed)
```

### データフロー図

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

ユーザー操作           -->  エントリ読み込み関数     -->  [成功] エントリデータ
(画面遷移/一覧表示)         (loadEntry/loadEntries等)       [失敗] Error オブジェクト
                                                              |
                                                              v
Error オブジェクト     -->  addNotification()          -->  Redux store
                           (i18nキー: ui.toast.               (notifications配列)
                            onFailToLoadEntries)               |
                                                              v
notifications          -->  Notifications コンポーネント -->  react-toastify
(Redux state)               (t() でi18n変換)                 トースト UI表示
                                                              (画面右上, 8秒で消去)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| entries.ts | `packages/decap-cms-core/src/actions/entries.ts` | ソース | エントリ管理アクション。`loadEntry`, `loadEntries`, `traverseCollectionCursor` がエラー時にトースト通知を発行 |
| editorialWorkflow.ts | `packages/decap-cms-core/src/actions/editorialWorkflow.ts` | ソース | 編集ワークフローアクション。`loadUnpublishedEntry`, `loadUnpublishedEntries` がエラー時にトースト通知を発行 |
| notifications.ts (actions) | `packages/decap-cms-core/src/actions/notifications.ts` | ソース | 通知アクション作成関数 |
| 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リソース | 英語ロケール定義 |
