# 通知設計書 15-メディア削除失敗通知

## 概要

本ドキュメントは、Decap CMS のメディアライブラリにおいて、メディアファイルの削除処理が失敗した場合に表示されるエラートースト通知の設計を記述する。

### 本通知の処理概要

メディア削除失敗通知は、ユーザーがメディアライブラリからメディアファイルを削除しようとした際に、何らかの理由で削除処理が失敗した場合にエラー情報をフィードバックするトースト通知である。アセットストアインテグレーション経由とバックエンド（Git）経由の2つの削除パスそれぞれに catch ブロックが設けられており、いずれの経路でも本通知が発火する。

**業務上の目的・背景**：メディアファイルの削除は、不要になったファイルの整理やストレージ容量の管理のために行われる操作である。削除が失敗した場合、ユーザーがファイルが削除されたと誤認し、依然として参照可能な状態であることに気づかない可能性がある。本通知により、削除失敗を即座に認知させ、再試行や管理者への報告を促す。

**通知の送信タイミング**：`deleteMedia` アクション内の catch ブロックで送信される。アセットストアインテグレーション経由の削除失敗時（行324-333）とバックエンド経由の削除失敗時（行354-363）の2箇所で発火する。

**通知の受信者**：メディア削除操作を実行した現在のログインユーザー（ブラウザ上のセッションユーザー）。クライアントサイドのアプリ内通知であるため、操作を実行した本人のみが受信する。

**通知内容の概要**：インライン文字列（i18nキーなし）で `"Failed to delete media: {error.message}"` というエラーメッセージが表示される。テンプレートリテラルでエラーオブジェクトの `message` プロパティが埋め込まれる。

**期待されるアクション**：ユーザーはエラー内容を確認し、ネットワーク接続の確認、ページのリロード、再度の削除試行、または管理者への報告を行うことが期待される。

## 通知種別

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

## 送信仕様

### 基本情報

| 項目 | 内容 |
|-----|------|
| 送信方式 | 同期（Redux dispatch） |
| 優先度 | 高 |
| リトライ | なし（通知自体のリトライはなし。削除操作の再試行はユーザー操作に依存） |

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

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

## 通知テンプレート

### メール通知の場合

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

### 本文テンプレート

```
Failed to delete media: {error.message}
```

注意：本通知はi18nキーを使用せず、インライン文字列（テンプレートリテラル）で直接メッセージを構成する。そのため多言語対応されていない。

### 添付ファイル

該当なし

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| error.message | エラーメッセージ文字列 | 削除処理中にスローされた Error オブジェクトの `message` プロパティ | Yes |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| 画面操作 | メディアライブラリからのファイル削除（アセットストア経由） | `provider.delete(file.id)` が例外をスローした場合 | deleteMedia thunk内のインテグレーション分岐の catch（行324-333） |
| 画面操作 | メディアライブラリからのファイル削除（バックエンド経由） | `backend.deleteMedia()` が例外をスローした場合 | deleteMedia thunk内のバックエンド分岐の catch（行354-363） |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| メディア削除処理が成功した場合 | 成功時は `mediaDeleted` アクションが dispatch され、本エラー通知は表示されない |
| ドラフトメディアの削除 | `file.draft` が true の場合、バックエンド通信なしに `removeAsset` と `removeDraftEntryMediaFile` で処理されるため、通信エラーは発生しない（行338-340） |

## 処理フロー

### 送信フロー

```mermaid
flowchart TD
    A[ユーザーがメディアファイル削除を実行] --> B[deleteMedia thunk dispatch]
    B --> C{インテグレーションあり?}
    C -->|あり| D[provider.delete - アセットストア経由]
    C -->|なし| E{file.draft?}
    E -->|Yes| F[removeAsset + removeDraftEntryMediaFile]
    E -->|No| G[backend.deleteMedia - バックエンド経由]
    D --> H{削除結果}
    H -->|成功| I[mediaDeleted dispatch]
    H -->|失敗| J[catch: addNotification - アセットストア]
    G --> K{削除結果}
    K -->|成功| L[mediaDeleted dispatch]
    K -->|失敗| M[catch: addNotification - バックエンド]
    J --> N[mediaDeleteFailed dispatch]
    M --> O[mediaDeleteFailed dispatch]
    N --> P[トースト表示 8秒後自動消去]
    O --> P
    I --> Q[終了]
    L --> Q
    F --> Q
    P --> Q
```

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

### 参照テーブル一覧

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

### 更新テーブル一覧

| テーブル名 | 操作 | 概要 |
|-----------|------|------|
| Redux Store: notifications | INSERT | 通知オブジェクトを notifications 配列に追加 |
| Redux Store: mediaLibrary | UPDATE | MEDIA_DELETE_FAILURE によりメディア削除リクエスト状態をリセット |

#### Redux Store 更新詳細

| 操作 | 項目 | 更新値 | 備考 |
|-----|------|-------|------|
| INSERT | notifications.notifications[] | `{ id: uuid(), message: 'Failed to delete media: {error.message}', type: 'error', dismissAfter: 8000 }` | message はインライン文字列 |

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 対処方法 |
|----------|---------|---------|
| アセットストア削除エラー | `provider.delete(file.id)` の通信エラー等 | エラー詳細をトースト通知で表示し、`mediaDeleteFailed({ privateUpload })` を dispatch |
| バックエンド削除エラー | `backend.deleteMedia(state.config, file.path)` の通信エラー等 | エラー詳細をトースト通知で表示し、`mediaDeleteFailed()` を dispatch |

### リトライ仕様

| 項目 | 内容 |
|-----|------|
| リトライ回数 | 0回（自動リトライなし） |
| リトライ間隔 | 該当なし |
| リトライ対象エラー | 該当なし（ユーザーによる手動再試行のみ） |

## 配信設定

### レート制限

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

### 配信時間帯

制限なし（ユーザー操作に応じて随時発生）

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

- エラーオブジェクトの `message` プロパティがそのままトースト通知に表示されるため、バックエンドの内部情報が漏洩するリスクがある。
- クライアントサイドのトースト通知であり、操作ユーザー本人のブラウザにのみ表示されるため、リスクは限定的である。

## 備考

- 本通知はi18nキーを使用せず、テンプレートリテラル ``Failed to delete media: ${error.message}`` で直接メッセージを構成する。No.14 のメディア保存失敗通知と同様に多言語対応されていない。
- No.14 とは異なり、`error.message`（Error オブジェクトの message プロパティ）を使用している点に注意。No.14 は `error` オブジェクト自体を文字列化している。
- `console.error(error)` でコンソールにもエラーが出力される。
- 2つの削除経路（アセットストア: 行324-333、バックエンド: 行354-363）それぞれに個別の catch ブロックがある。
- `dismissAfter: 8000` が設定されており、8秒後に自動消去される。

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | notifications.ts (actions) | `packages/decap-cms-core/src/actions/notifications.ts` | `NotificationPayload` の `message` フィールドが `string | NotificationMessage` の union 型であることを理解する。本通知では `string` 型が使用される |
| 1-2 | mediaLibrary.ts | `packages/decap-cms-core/src/actions/mediaLibrary.ts` | `MediaFile` 型と `MediaOptions` インターフェース（行437-444）を理解する |

**読解のコツ**: 本通知では `error.message` を使用しているが、No.14（メディア保存失敗通知）では `error` をそのまま使用している。この違いに注意。

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | mediaLibrary.ts | `packages/decap-cms-core/src/actions/mediaLibrary.ts` | `deleteMedia` 関数（行311-366）がメディア削除処理のエントリーポイント。`file` と `opts` を受け取る |

**主要処理フロー**:
1. **行315-316**: バックエンドとインテグレーションの取得
2. **行317-335**: アセットストアインテグレーション経由の削除パス
   - **行319**: `mediaDeleting` を dispatch
   - **行322**: `provider.delete(file.id)` を呼び出し
   - **行324-333**: catch ブロック（**通知発火箇所1**）
3. **行337-365**: バックエンド経由の削除パス
   - **行338-340**: ドラフトメディアの場合は `removeAsset` + `removeDraftEntryMediaFile` のみ
   - **行342-352**: 非ドラフトメディアの場合は `backend.deleteMedia` を呼び出し
   - **行354-363**: catch ブロック（**通知発火箇所2**）

#### 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` | 行35-37: `typeof notification.message == 'string'` の分岐で文字列メッセージがそのまま表示される |

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

```
deleteMedia (mediaLibrary.ts:311)
    |
    +-- [インテグレーション経由]
    |       |
    |       +-- mediaDeleting (mediaLibrary.ts:475)
    |       +-- provider.delete(file.id)
    |       +-- [成功時] mediaDeleted (mediaLibrary.ts:479)
    |       +-- [失敗時] catch (mediaLibrary.ts:324)
    |               +-- console.error(error)
    |               +-- addNotification (notifications.ts:18) -- インライン文字列
    |               +-- mediaDeleteFailed (mediaLibrary.ts:487)
    |
    +-- [バックエンド経由]
            |
            +-- [ドラフト] removeAsset + removeDraftEntryMediaFile
            |
            +-- [非ドラフト]
                    +-- mediaDeleting (mediaLibrary.ts:475)
                    +-- removeAsset
                    +-- backend.deleteMedia
                    +-- [成功時] mediaDeleted
                    +-- [失敗時] catch (mediaLibrary.ts:354)
                            +-- console.error(error)
                            +-- addNotification -- インライン文字列
                            +-- mediaDeleteFailed
```

### データフロー図

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

ユーザー削除操作 ──────> deleteMedia thunk ───────────────> Redux dispatch
  (MediaFile, opts)              |                                    |
                         インテグレーション判定                        |
                                 |                                    |
                         provider.delete /                     NOTIFICATION_SEND
                         backend.deleteMedia                          |
                                 |                            reducer: notifications
                         [例外スロー]                                 |
                                 |                            Notifications コンポーネント
                         addNotification({                           |
                           message: `Failed to               react-toastify toast()
                             delete media:                           |
                             ${error.message}`,              トースト表示（8秒後自動消去）
                           type: 'error',
                           dismissAfter: 8000
                         })
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| mediaLibrary.ts | `packages/decap-cms-core/src/actions/mediaLibrary.ts` | ソース | メディア削除処理のthunkアクション。2箇所の catch ブロックで通知を 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コンポーネント |
| entries.ts (actions) | `packages/decap-cms-core/src/actions/entries.ts` | ソース | `removeDraftEntryMediaFile` アクション（ドラフトメディア削除時に使用） |
| media.ts (actions) | `packages/decap-cms-core/src/actions/media.ts` | ソース | `removeAsset` アクション |
