# 画面設計書 95-返信フォーム

## 概要

本ドキュメントは、Ghost公開画面における「返信フォーム」（ReplyForm）の画面設計書です。既存のコメントに対して返信を投稿するためのフォームコンポーネントです。

### 本画面の処理概要

返信フォームは、コメント一覧内の特定のコメントに対して返信を投稿するためのインラインフォームです。

**業務上の目的・背景**：コメントに対するスレッド形式の議論を可能にし、より深いコミュニティ参加を促進します。返信機能により、質問と回答、議論の展開などの対話的なコミュニケーションを実現します。

**画面へのアクセス方法**：
- コメントの「Reply」ボタンをクリック
- 返信への返信（ネストされた返信）も同様の方法でアクセス

**主要な操作・処理内容**：
1. リッチテキストエディタでの返信入力
2. 「Add reply」/「Reply」ボタンで返信送信
3. 「Cancel」ボタンでフォームを閉じる
4. Cmd/Ctrl + Enterで返信送信
5. ESCでフォームを閉じる

**画面遷移**：
- 送信後: コメント一覧に返信が追加され、フォームが閉じる
- キャンセル: フォームが閉じる

**権限による表示制御**：
- ログイン済み会員のみが返信可能
- 名前未設定の会員は、フォーカス時に詳細追加ポップアップが表示される

## 関連機能

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

## 画面種別

登録（フォーム）

## URL/ルーティング

- コメント一覧内に動的に表示（独自URLなし）

## 入出力項目

| 項目名 | 入出力 | データ型 | 必須 | 説明 |
|--------|--------|----------|------|------|
| html | 入力 | string | 必須 | 返信本文（HTML形式） |
| post_id | 入力 | string | 必須 | 対象投稿のID |
| in_reply_to_id | 入力 | string | 必須 | 返信先コメントのID |
| parent | 入力 | Comment | 必須 | 親コメント（トップレベルコメント） |
| status | 入力 | string | 必須 | ステータス（'published'固定） |

## 表示項目

| 項目名 | データ型 | 説明 |
|--------|----------|------|
| member.avatar_image | string | 会員のアバター画像 |
| member.name | string | 会員の名前 |
| member.expertise | string | 会員の専門分野 |
| openForm.in_reply_to_snippet | string | 返信先コメントのスニペット |
| placeholder | string | エディタのプレースホルダー |
| submitText | ReactNode | 送信ボタンのテキスト |

## イベント仕様

### 1-返信送信（submit）

「Add reply」ボタンクリックまたはCmd/Ctrl + Enterで返信を送信します。

- 処理: `dispatchAction('addReply', {parent, reply: {post_id, in_reply_to_id, status: 'published', html}})`
- 成功時: 親コメントのrepliesに新規返信追加、フォームが閉じる
- 失敗時: エラー状態をセット

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

「Cancel」ボタンクリックまたはESCキーでフォームを閉じます。

- 処理: `dispatchAction('closeCommentForm', openForm.id)`
- 結果: openCommentFormsからフォームが削除される

### 3-返信先表示

返信への返信（ネストされた返信）の場合、返信先のスニペットを表示します。

- 表示: `openForm.in_reply_to_snippet` が設定されている場合
- 形式: "Reply to: {スニペット}"

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

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

| 操作（イベント） | 対象テーブル | 操作種別 | 概要 |
|----------------|-------------|---------|------|
| 返信送信 | comments | INSERT | 返信コメントの作成 |

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

#### comments

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | post_id | 対象投稿のID | コンテキストから取得 |
| INSERT | member_id | ログイン中の会員ID | セッションから取得 |
| INSERT | parent_id | 親コメント（トップレベル）のID | parentから取得 |
| INSERT | in_reply_to_id | 直接の返信先コメントのID | openForm.in_reply_to_idから取得 |
| INSERT | html | エディタから取得したHTML | Tiptapエディタの出力 |
| INSERT | status | 'published' | 固定値 |
| INSERT | created_at | 現在日時 | サーバー側で設定 |

## メッセージ仕様

| メッセージID | 種別 | 表示条件 | メッセージ内容 |
|-------------|------|---------|--------------|
| MSG001 | プレースホルダー | 常時 | "Reply to comment" |
| MSG002 | ボタン | PC表示 | "Add reply" |
| MSG003 | ボタン | モバイル表示 | "Reply" |
| MSG004 | ボタン | 常時 | "Cancel" |
| MSG005 | ラベル | 返信への返信時 | "Reply to: {スニペット}" |

## 例外処理

| 例外種別 | 発生条件 | 対応処理 |
|---------|---------|---------|
| 送信エラー | API呼び出し失敗 | progress: 'error' をセット |
| 空の返信 | editor.isEmpty が true | 送信ボタンを無効化 |

## 備考

- 返信は親コメントの下にネストして表示される
- 返信への返信も同様のフォームを使用
- モバイル表示時はreducedモードで表示（isMobile()で判定）
- フォームはスクロールして表示位置に移動（scrollToElement）

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | app-context.ts | `apps/comments-ui/src/app-context.ts` | OpenCommentForm型（L35-42） |

**読解のコツ**: OpenCommentForm型はid, parent_id, in_reply_to_id, type('reply'/'edit')を持つ。

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | reply-form.tsx | `apps/comments-ui/src/components/content/forms/reply-form.tsx` | 返信フォームコンポーネント（1-66行目） |

**主要処理フロー**:
1. **8-11行目**: Props型定義（openForm, parent）
2. **14行目**: postId, dispatchActionをコンテキストから取得
3. **15行目**: useRefCallbackでスクロール処理
4. **17-20行目**: editorConfig設定（プレースホルダー: "Reply to comment", オートフォーカス: true）
5. **22行目**: useEditorフックでエディタ取得
6. **24-35行目**: submit関数でaddReplyアクションを実行
7. **37-39行目**: close関数でcloseCommentFormアクションを実行
8. **41-43行目**: submitText設定
9. **45-62行目**: JSXレンダリング

#### Step 3: 共通フォームコンポーネントを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | form.tsx | `apps/comments-ui/src/components/content/forms/form.tsx` | FormWrapper, Form, FormHeader（返信先表示） |

**主要処理フロー**:
- **207-212行目**: FormHeaderで返信先スニペット表示

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

```
Comment (コメントコンポーネント)
    │
    └─ ReplyForm (返信ボタンクリック時に展開)
           │
           ├─ useRefCallback(scrollToElement) - スクロール処理
           │
           ├─ useEditor() - Tiptapエディタ取得
           │
           ├─ submit()
           │      └─ dispatchAction('addReply', {parent, reply})
           │             └─ API: POST /comments (parent_id, in_reply_to_id付き)
           │
           ├─ close()
           │      └─ dispatchAction('closeCommentForm', openForm.id)
           │
           └─ FormWrapper
                  │
                  ├─ Avatar (member)
                  │
                  ├─ FormHeader
                  │      └─ replyingToText (返信先スニペット)
                  │
                  └─ Form
                         └─ FormEditor
                                ├─ EditorContent (Tiptap)
                                ├─ Cancel button
                                └─ Submit button
```

### データフロー図

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

エディタ入力 ────────────▶ editor.getHTML() ────────▶ html
                           │
postId (context) ─────────│
                           │
parent.id ────────────────│
                           │
openForm.in_reply_to_id ──│
                           │
status: 'published' ──────│
                           ▼
                        submit()
                           │
                           ▼
                   dispatchAction('addReply')
                           │
                           ▼
                   API: POST /comments ──────────▶ 返信コメント作成
                                                  parent.repliesに追加
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| reply-form.tsx | `apps/comments-ui/src/components/content/forms/reply-form.tsx` | ソース | 返信フォームコンポーネント |
| form.tsx | `apps/comments-ui/src/components/content/forms/form.tsx` | ソース | 共通フォームコンポーネント |
| app-context.ts | `apps/comments-ui/src/app-context.ts` | ソース | OpenCommentForm型定義 |
| hooks.ts | `apps/comments-ui/src/utils/hooks.ts` | ソース | useEditor, useRefCallback |
| helpers.ts | `apps/comments-ui/src/utils/helpers.ts` | ソース | scrollToElement, isMobile |
| comment.tsx | `apps/comments-ui/src/components/content/comment.tsx` | ソース | 親のコメントコンポーネント |
| actions.ts | `apps/comments-ui/src/actions.ts` | ソース | addReply, closeCommentFormアクション |
