# 画面設計書 99-詳細追加ポップアップ

## 概要

本ドキュメントは、Ghost公開画面における「詳細追加ポップアップ」（AddDetailsPopup）の画面設計書です。コメント投稿前に会員の名前と専門分野（Expertise）を入力・編集するためのポップアップコンポーネントです。

### 本画面の処理概要

詳細追加ポップアップは、コメント投稿時に会員プロフィール（名前・専門分野）を入力・更新するためのモーダルダイアログです。

**業務上の目的・背景**：コメント欄に表示される投稿者情報を充実させ、コミュニティでの信頼性・識別性を高めます。専門分野を追加することで、コメントの文脈や信頼性を読者に伝えることができます。サンプルプロフィールを表示することで、入力のイメージを具体的に示します。

**画面へのアクセス方法**：
- 名前未設定の会員がコメントフォームにフォーカスした場合（自動表示）
- フォームヘッダーの名前または専門分野をクリックした場合
- 初回コメント投稿時（名前未設定の場合）

**主要な操作・処理内容**：
1. 名前の入力（必須、最大64文字）
2. 専門分野の入力（任意、最大50文字）
3. 「Save」ボタンでプロフィールを保存
4. Enterキーで保存
5. 閉じるボタンまたは背景クリックでキャンセル

**画面遷移**：
- 保存後: ポップアップが閉じ、コメントフォームにフォーカスが戻る
- キャンセル: ポップアップが閉じる（callback(false)が呼ばれる）

**権限による表示制御**：
- ログイン中の会員のみに表示
- 会員情報が既にある場合は、その値が初期値として表示される

## 関連機能

| 機能No | 機能名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 25 | 記事コメント | 主機能 | コメント投稿者の詳細情報追加 |
| 72 | Comments UI | 補助機能 | コメントUIウィジェットの表示 |

## 画面種別

登録/編集（ポップアップ/モーダル）

## URL/ルーティング

- ポップアップとして表示（独自URLなし）

## 入出力項目

| 項目名 | 入出力 | データ型 | 必須 | 最大長 | 説明 |
|--------|--------|----------|------|--------|------|
| name | 入出力 | string | 必須 | 64 | 会員の名前 |
| expertise | 入出力 | string | 任意 | 50 | 会員の専門分野 |

## 表示項目

| 項目名 | データ型 | 説明 |
|--------|----------|------|
| member.name | string | 現在の名前（初期値） |
| member.expertise | string | 現在の専門分野（初期値） |
| exampleProfiles | Array | サンプルプロフィール一覧 |
| expertiseCharsLeft | number | 専門分野の残り文字数 |
| error.name | string | 名前フィールドのエラーメッセージ |

## イベント仕様

### 1-保存（submit）

「Save」ボタンクリックまたはEnterキーでプロフィールを保存します。

- バリデーション: 名前が空でないこと（トリム後）
- 処理: `dispatchAction('updateMember', {name, expertise})`
- 成功時: `callback(true)`を呼び出し、ポップアップを閉じる
- バリデーションエラー: エラーメッセージを表示、名前フィールドにフォーカス

### 2-キャンセル（close）

閉じるボタンクリックまたはポップアップ外クリックでキャンセルします。

- 処理: `dispatchAction('closePopup', {})` → `callback(false)`
- 結果: ポップアップが閉じ、変更は保存されない

### 3-文字数カウント

専門分野の入力に伴い、残り文字数を更新します。

- 処理: `setExpertiseCharsLeft(maxExpertiseChars - text.length)`
- 表示: "{amount} characters left"形式で表示
- 0文字の場合: 赤色で警告表示

### 4-オートフォーカス

ポップアップ表示時に、適切なフィールドにフォーカスします。

- props.expertiseAutofocus が true の場合: 専門分野フィールドにフォーカス
- それ以外: 名前フィールドにフォーカス
- モバイルでは無効（isMobile()判定）
- 200msの遅延後にフォーカス（トランジション対応）

## データベース更新仕様

### 操作別データベース影響一覧

| 操作（イベント） | 対象テーブル | 操作種別 | 概要 |
|----------------|-------------|---------|------|
| 保存 | members | UPDATE | 会員情報の更新 |

### テーブル別更新項目詳細

#### members

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| UPDATE | id | ログイン中の会員ID | WHERE条件 |
| UPDATE | name | 入力された名前 | トリムされた値 |
| UPDATE | expertise | 入力された専門分野 | 任意項目 |

## メッセージ仕様

| メッセージID | 種別 | 表示条件 | メッセージ内容 |
|-------------|------|---------|--------------|
| MSG001 | タイトル | 常時 | "Complete your profile." |
| MSG002 | 説明 | 常時 | "Add context to your comment, share your name and expertise to foster a healthy discussion." |
| MSG003 | ラベル | 常時 | "Name" |
| MSG004 | ラベル | 常時 | "Expertise" |
| MSG005 | プレースホルダー | 名前 | "Jamie Larson" |
| MSG006 | プレースホルダー | 専門分野 | "Head of Marketing at Acme, Inc" |
| MSG007 | 文字数 | 常時 | "{amount} characters left" |
| MSG008 | エラー | 名前が空 | "Enter your name" |
| MSG009 | ボタン | 常時 | "Save" |

## 例外処理

| 例外種別 | 発生条件 | 対応処理 |
|---------|---------|---------|
| バリデーションエラー | 名前が空（トリム後） | エラーメッセージ表示、フィールドにフォーカス |
| API呼び出し失敗 | updateMemberエラー | 例外がスローされる（コンソールにエラー） |

## 備考

- サンプルプロフィールは4件表示（James Fletcher, Naomi Schiff, Franz Tost, Katrina Klosp）
- サンプルプロフィールの画像はrandomuser.meからの外部URL
- 専門分野のi18n対応サンプル: "Full-time parent", "Founder @ Acme Inc", "Neurosurgeon", "Local resident"
- モバイルではサンプルプロフィール部分が非表示（hidden sm:block）
- アクセントカラー（accentColor）が保存ボタンの背景色に適用される

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | app-context.ts | `apps/comments-ui/src/app-context.ts` | Member型（name, expertise含む） |

**読解のコツ**: Member型にname, expertiseフィールドがあることを確認。

#### Step 2: AddDetailsPopupコンポーネントを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | add-details-popup.tsx | `apps/comments-ui/src/components/popups/add-details-popup.tsx` | 詳細追加ポップアップコンポーネント（1-213行目） |

**主要処理フロー**:
1. **8-11行目**: Props型定義（callback, expertiseAutofocus）
2. **13-15行目**: 入力参照のref、コンテキスト取得
3. **17-25行目**: 状態の初期化（name, expertise, expertiseCharsLeft）
4. **27行目**: エラー状態の初期化
5. **29-31行目**: stopPropagation関数
6. **33-36行目**: close関数（callback(false)付き）
7. **38-49行目**: submit関数（バリデーション＋updateMember）
8. **52-67行目**: useEffectでオートフォーカス設定
9. **69-112行目**: renderExampleProfiles関数（サンプルプロフィール表示）
10. **114-116行目**: 残り文字数テキストの生成
11. **118-209行目**: JSXレンダリング

#### Step 3: 呼び出し元（FormWrapper）を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | form.tsx | `apps/comments-ui/src/components/content/forms/form.tsx` | FormWrapperのopenEditDetails関数（L317-336） |

**主要処理フロー**:
- **317-336行目**: openEditDetails関数でaddDetailsPopupを開く
- **323-334行目**: callbackでエディタのフォーカス制御

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

```
FormWrapper
    │
    ├─ editName() → openEditDetails({expertiseAutofocus: false})
    │
    └─ editExpertise() → openEditDetails({expertiseAutofocus: true})
           │
           └─ dispatchAction('openPopup', {type: 'addDetailsPopup', ...})
                  │
                  └─ AddDetailsPopup
                         │
                         ├─ useState (name, expertise, expertiseCharsLeft, error)
                         │
                         ├─ useEffect (オートフォーカス)
                         │
                         ├─ renderExampleProfiles()
                         │      └─ サンプルプロフィール4件表示
                         │
                         ├─ submit()
                         │      ├─ バリデーション（名前必須）
                         │      ├─ dispatchAction('updateMember', {name, expertise})
                         │      │      └─ API: PUT /members/current
                         │      └─ close(true)
                         │
                         └─ close(succeeded)
                                ├─ dispatchAction('closePopup', {})
                                └─ callback(succeeded)
```

### データフロー図

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

member.name ─────────────▶ useState(name) ───────────▶ 入力フィールド初期値
member.expertise ─────────▶ useState(expertise) ─────▶ 入力フィールド初期値
                           │
名前入力 ─────────────────▶ setName() ────────────────▶ 名前状態更新
専門分野入力 ─────────────▶ setExpertise() ───────────▶ 専門分野状態更新
                           │                          expertiseCharsLeft更新
                           │
Saveボタン/Enter ─────────▶ submit()
                           │
                           ├─ バリデーション
                           │      └─ エラー時 → setError(), フォーカス
                           │
                           └─ dispatchAction('updateMember')
                                  │
                                  ▼
                           API: PUT /members/current ──▶ 会員情報更新
                                  │
                           close(true) ────────────────▶ ポップアップ閉じる
                                  │                      callback(true)
                                  ▼
                           エディタにフォーカス戻る
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| add-details-popup.tsx | `apps/comments-ui/src/components/popups/add-details-popup.tsx` | ソース | 詳細追加ポップアップコンポーネント |
| close-button.tsx | `apps/comments-ui/src/components/popups/close-button.tsx` | ソース | 閉じるボタンコンポーネント |
| form.tsx | `apps/comments-ui/src/components/content/forms/form.tsx` | ソース | 呼び出し元（FormWrapper） |
| app-context.ts | `apps/comments-ui/src/app-context.ts` | ソース | Member型、accentColor |
| helpers.ts | `apps/comments-ui/src/utils/helpers.ts` | ソース | isMobileユーティリティ |
| actions.ts | `apps/comments-ui/src/actions.ts` | ソース | updateMember, closePopupアクション |
