# 通知設計書 3-必須フィールド未入力通知

## 概要

本ドキュメントは、Decap CMSにおけるエントリ保存時に必須フィールドが未入力の場合に表示されるエラートースト通知の設計仕様を定義する。コンテンツ編集画面でユーザーが保存ボタンを押下した際、バリデーションエラー（PRESENCE型）が存在する場合にユーザーに入力漏れを通知する仕組みの詳細を記載する。

### 本通知の処理概要

本通知は、Decap CMSのエントリ編集画面においてユーザーが保存操作を行った際、バリデーションチェックの結果、必須フィールドに値が入力されていない場合に発火するエラートースト通知である。`persistEntry`（通常保存）および `persistUnpublishedEntry`（編集ワークフロー経由の保存）の両関数で、`fieldsErrors` が空でなく、かつ `PRESENCE` タイプのバリデーションエラーが含まれる場合に通知が発行される。バックエンドへの永続化処理は実行されず、即座にPromise.reject()が返される。

**業務上の目的・背景**：コンテンツ編集者がエントリを不完全な状態で保存しようとした場合に、必須フィールドの入力漏れを警告し、データの整合性を保つ。CMS上のコンテンツ品質を維持するためのフロントエンドバリデーション通知であり、不完全なデータがバックエンドに送信されることを防止する。

**通知の送信タイミング**：ユーザーがエントリの保存ボタンを押下し、`persistEntry` または `persistUnpublishedEntry` 関数が実行された直後、`fieldsErrors` の存在チェックで必須フィールドエラーが検出された時点で通知が発行される。バックエンドAPI呼び出しの前段階で発火する。

**通知の受信者**：エントリの保存操作を行った当該ユーザー本人。ブラウザ画面上のトースト通知として表示される。

**通知内容の概要**：i18nキー `ui.toast.missingRequiredField` に基づくメッセージが表示される。英語ロケールでは `Oops, you've missed a required field. Please complete before saving.` という固定メッセージが表示される。動的パラメータは含まれない。

**期待されるアクション**：ユーザーはトースト通知を確認し、エントリ編集フォーム上でハイライトされている必須フィールドに値を入力してから再度保存操作を行うことが期待される。

## 通知種別

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

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

## 送信仕様

### 基本情報

| 項目 | 内容 |
|-----|------|
| 送信方式 | 同期（Redux dispatch → react-toastify） |
| 優先度 | 中（ユーザー操作のフィードバック） |
| リトライ | なし（UIトースト通知のため再送不要） |

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

エントリ保存操作を実行したユーザーのブラウザセッションに対して直接表示する。

## 通知テンプレート

### メール通知の場合

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

### 本文テンプレート

```
Oops, you've missed a required field. Please complete before saving.
```

i18nキー `ui.toast.missingRequiredField` の英語テンプレート。動的変数を含まない固定メッセージ。

### 添付ファイル

該当なし

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| （なし） | 本通知には動的変数がない | - | - |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| 画面操作 | エントリ保存ボタン押下 | `persistEntry` 内で `fieldsErrors` が空でなく、かつ `PRESENCE` タイプのエラーが含まれる | 通常のエントリ保存時（entries.ts 901-918行目） |
| 画面操作 | エントリ保存ボタン押下（ワークフロー） | `persistUnpublishedEntry` 内で `fieldsErrors` が空でなく、かつ `PRESENCE` タイプのエラーが含まれる | 編集ワークフロー経由の保存時（editorialWorkflow.ts 337-353行目） |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| fieldsErrors が空の場合 | バリデーションエラーが無い場合は通知を発行せずバックエンドへの保存処理に進む |
| fieldsErrors にPRESENCEタイプのエラーが含まれない場合 | 必須フィールド以外のバリデーションエラー（カスタムバリデーション等）のみの場合は本通知は発行されない。ただし保存処理自体はPromise.reject()で中断される |

## 処理フロー

### 送信フロー

```mermaid
flowchart TD
    A[ユーザーが保存ボタン押下] --> B[persistEntry / persistUnpublishedEntry 発火]
    B --> C[entryDraft.fieldsErrors を取得]
    C --> D{fieldsErrors は空か？}
    D -->|空| E[バックエンド永続化処理へ進む]
    D -->|空でない| F{PRESENCE タイプのエラーが含まれるか？}
    F -->|含まれる| G[dispatch addNotification - エラートースト表示]
    F -->|含まれない| H[トースト通知なし]
    G --> I[Promise.reject で処理中断]
    H --> I
    I --> J[Notifications コンポーネントがトースト描画]
    J --> K[8秒後に自動消去]
    K --> L[終了]
    E --> L
```

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

### 参照テーブル一覧

該当なし（本通知はクライアントサイドのRedux storeのみを使用。バリデーションエラーはRedux stateの `entryDraft.fieldsErrors` から参照される。）

### 更新テーブル一覧

該当なし（バリデーションエラーが検出された場合、バックエンドへの永続化処理は実行されない）

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 対処方法 |
|----------|---------|---------|
| バリデーションエラー（PRESENCE） | 必須フィールドが未入力 | エラートースト通知を表示し、Promise.reject()で保存処理を中断 |

### リトライ仕様

| 項目 | 内容 |
|-----|------|
| リトライ回数 | なし（自動リトライは不適切。ユーザーによる入力修正が必要） |
| リトライ間隔 | 該当なし |
| リトライ対象エラー | 該当なし |

## 配信設定

### レート制限

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

### 配信時間帯

制限なし（保存操作のタイミングで即座に表示）

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

- 固定メッセージのみが表示されるため、セキュリティ上の懸念は低い
- どのフィールドが未入力かの詳細はトースト通知ではなく、エントリ編集フォーム上のフィールドバリデーションUIで表示される

## 備考

- `ValidationErrorTypes.PRESENCE` は `constants/validationErrorTypes` で定義されるバリデーションエラー種別の一つ
- `fieldsErrors` にPRESENCEタイプ以外のエラー（CUSTOMタイプ等）のみが含まれる場合、この通知は表示されないが、保存処理はPromise.reject()で中断される
- `persistEntry`（entries.ts）と `persistUnpublishedEntry`（editorialWorkflow.ts）で同一のバリデーションロジックとi18nキーが使用されている

---

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

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

### 推奨読解順序

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

バリデーションエラーのデータ構造を把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | validationErrorTypes | `packages/decap-cms-core/src/constants/validationErrorTypes` | `PRESENCE` を含むバリデーションエラー種別の定数定義を理解する |
| 1-2 | entries.ts | `packages/decap-cms-core/src/actions/entries.ts` | 425-433行目: `changeDraftFieldValidation` で `errors` 配列に `type`, `parentIds`, `message` を含むオブジェクトが格納される構造を理解する |

**読解のコツ**: `fieldsErrors` は Immutable.js の Map であり、`.isEmpty()` や `.some()` メソッドが使用されている。Immutable.js の API に慣れていることが前提。

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

保存処理の起点を特定する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | entries.ts | `packages/decap-cms-core/src/actions/entries.ts` | `persistEntry` 関数（893-977行目）がエントリ保存のエントリーポイント。特に897-918行目のバリデーションチェック部分に注目。 |

**主要処理フロー**:
1. **896-897行目**: `entryDraft` と `fieldsErrors` を取得
2. **901行目**: `fieldsErrors.isEmpty()` でバリデーションエラーの存在チェック
3. **902-903行目**: `errors.some(error => error.type === ValidationErrorTypes.PRESENCE)` でPRESENCEタイプの存在チェック
4. **906-915行目**: `addNotification` でエラートースト通知を発行
5. **918行目**: `Promise.reject()` で保存処理を中断

#### Step 3: 編集ワークフロー側のエントリーポイントを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | editorialWorkflow.ts | `packages/decap-cms-core/src/actions/editorialWorkflow.ts` | `persistUnpublishedEntry` 関数（323-409行目）。337-353行目のバリデーションチェックは `persistEntry` と同一ロジック。 |

**主要処理フロー**:
- **337行目**: `fieldsErrors.isEmpty()` チェック
- **338-339行目**: PRESENCEタイプのエラー判定
- **343-350行目**: `addNotification` でエラートースト通知発行
- **353行目**: `Promise.reject()` で保存処理を中断

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | index.js (en) | `packages/decap-cms-locales/src/en/index.js` | 274行目: `missingRequiredField: "Oops, you've missed a required field. Please complete before saving."` - 動的変数なしの固定メッセージ |

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

```
persistEntry (entries.ts:893)
    |
    +-- entryDraft.get('fieldsErrors') 取得 (entries.ts:897)
    |
    +-- [fieldsErrors非空 かつ PRESENCE含む]
    |       |
    |       +-- dispatch(addNotification({key:'ui.toast.missingRequiredField'}))
    |       |       |
    |       |       +-- [Redux] NOTIFICATION_SEND アクション
    |       |               +-- notifications reducer
    |       |                       +-- Notifications コンポーネント → toast()
    |       |
    |       +-- return Promise.reject()
    |
    +-- [fieldsErrors空]
            +-- バックエンド永続化処理へ続行

persistUnpublishedEntry (editorialWorkflow.ts:323)
    |
    +-- entryDraft.get('fieldsErrors') 取得
    |
    +-- [同一ロジック]
            +-- dispatch(addNotification({key:'ui.toast.missingRequiredField'}))
            +-- return Promise.reject()
```

### データフロー図

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

保存ボタン押下         -->  fieldsErrors チェック      -->  [エラーなし] バックエンドへ保存
                                                           [PRESENCEエラーあり]
                                                              |
                                                              v
(固定メッセージ)       -->  addNotification()          -->  Redux store
                           (i18nキー: ui.toast.               (notifications配列)
                            missingRequiredField)              |
                                                              v
notifications          -->  Notifications コンポーネント -->  react-toastify
(Redux state)               (t() でi18n変換)                 トースト UI表示
                                                              (画面右上, 8秒で消去)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| entries.ts | `packages/decap-cms-core/src/actions/entries.ts` | ソース | `persistEntry` 関数内でバリデーションチェックと通知発行 |
| editorialWorkflow.ts | `packages/decap-cms-core/src/actions/editorialWorkflow.ts` | ソース | `persistUnpublishedEntry` 関数内でバリデーションチェックと通知発行 |
| validationErrorTypes | `packages/decap-cms-core/src/constants/validationErrorTypes` | 定数定義 | `PRESENCE` 等のバリデーションエラー種別定数 |
| 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リソース | 英語ロケール定義 |
