# 画面設計書 67-通知画面

## 概要

本ドキュメントは、Ghost管理画面における「通知画面（Notifications）」の設計仕様を記述したものである。この画面では、ActivityPubネットワーク上での自分のアクティビティに対する反応（いいね、リポスト、フォロー、返信、メンション）を一覧表示する。

### 本画面の処理概要

通知画面は、他のユーザーからのインタラクションを時系列で表示する通知センターである。同じ投稿への複数のいいねや、同時期の複数のフォローなどは自動的にグループ化され、効率的に確認できる。

**業務上の目的・背景**：ソーシャルウェブでの活動において、フォロワーからの反応を把握することは重要である。この画面により、いいね、リポスト、フォロー、返信、メンションを一か所で確認でき、コミュニティとのエンゲージメントを維持できる。グループ化機能により、人気のある投稿への大量の反応も効率的に確認できる。

**画面へのアクセス方法**：Ghost管理画面のサイドバーから「Social」セクションへアクセスし、「Notifications」を選択。URL は `/ghost/#/activitypub/notifications`。

**主要な操作・処理内容**：
1. 通知一覧の表示（時系列・グループ化）
2. 通知タイプ別のアイコン・表示形式
3. グループ化された通知の展開/折りたたみ
4. 通知クリックによる関連コンテンツへの遷移
5. フォローバック操作
6. 無限スクロールによる追加読み込み

**画面遷移**：サイドバーからアクセス。各通知クリックで該当ノート/記事/プロフィールへ遷移。

**権限による表示制御**：ActivityPub機能が有効化されている場合にのみ表示される。スタッフユーザーがアクセス可能。

## 関連機能

| 機能No | 機能名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 57 | ActivityPub | 主機能 | ActivityPub通知の一覧表示 |

## 画面種別

一覧画面（グループ化対応）

## URL/ルーティング

- メインパス: `/ghost/#/activitypub/notifications`
- ページタイトル: 「Notifications」

## 入出力項目

この画面には直接の入力項目はない。

## 表示項目

### 通知一覧項目

| 項目名 | 表示形式 | 説明 |
|--------|----------|------|
| 通知アイコン | アイコン | 通知タイプ別アイコン（ハート/リポスト/ユーザー等） |
| アバター | 画像 | 通知送信者のアバター（単一の場合） |
| 複数アバター | 画像群 | グループ化された場合の複数アバター（最大5件） |
| 送信者名 | テキスト | 「{名前} and X others」形式 |
| アクション説明 | テキスト | 「liked your post」「followed you」等 |
| 通知日時 | テキスト | 相対時間表示 |
| 対象コンテンツ | テキスト/プレビュー | いいね/リポストの対象投稿プレビュー |
| 返信内容 | テキスト | 返信通知の場合の返信本文 |
| フォローボタン | ボタン | フォローバック用ボタン |
| 展開ボタン | ボタン | グループ化通知の展開/折りたたみ |

### 通知タイプ別アイコン

| タイプ | アイコン | 説明 |
|--------|---------|------|
| like | ハート | いいね通知 |
| repost | リポスト | リポスト通知 |
| follow | ユーザー+ | フォロー通知 |
| reply | 返信 | 返信通知 |
| mention | @ | メンション通知 |

## イベント仕様

### 1-通知クリック

1. 通知アイテムをクリック
2. 通知タイプに応じて遷移先を決定
   - like/repost: 対象投稿の詳細へ（Article→Reader、Note→Notes）
   - reply/mention: 返信/メンションのノート詳細へ
   - follow（単一）: 送信者のプロフィールへ
   - follow（複数）: グループ展開

### 2-グループ展開/折りたたみ

1. 展開ボタン（ChevronDown）をクリック
2. `toggleOpen(groupId)` を実行
3. `openStates[groupId]` を反転
4. 展開時：全送信者リストを表示
5. 折りたたみ時：アバター群表示に戻る

### 3-フォローバック

1. FollowButton をクリック
2. フォロー API を呼び出し
3. 成功時：ボタン状態を「フォロー中」に更新
4. 失敗時：エラートースト表示

### 4-無限スクロール

1. IntersectionObserver がロードトリガー位置を検知
2. `hasNextPage` が true かつ `isFetchingNextPage` が false の場合
3. `fetchNextPage` を実行
4. 次ページの通知を取得
5. 既存リストに追加（グループ化処理を再適用）

### 5-プロフィール遷移

1. 送信者名またはアバターをクリック
2. `handleProfileClick(actor.handle, navigate)` を実行
3. `/profile/{handle}` へ遷移

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

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

この画面からの直接的なGhostデータベース更新はなし。ActivityPub APIを経由したリモートサーバーとの通信が行われる。

| 操作（イベント） | 対象 | 操作種別 | 概要 |
|----------------|------|---------|------|
| 通知取得 | ActivityPub API | GET | 通知一覧の取得 |
| フォロー | ActivityPub API | POST | Follow操作 |

## メッセージ仕様

| メッセージ種別 | メッセージ内容 | 表示条件 |
|---------------|---------------|----------|
| 情報 | Quiet for now, but not for long! | 通知が空の場合 |
| 情報 | When someone likes, boosts, or replies to you, you'll find it here. | 空通知時の説明 |

## 例外処理

| 例外条件 | 処理内容 |
|---------|---------|
| API エラー | AppError コンポーネントでエラーコード・ステータスコードを表示 |
| ネットワークエラー | ローディング表示継続 |

## 備考

- 通知は24時間単位の時間バケットでグループ化される
- 同じ投稿への複数のいいねは1つのグループにまとめられる
- 連続した同タイプの通知がグループ化される（sequenceCounter）
- 返信（reply）とメンション（mention）はグループ化されない
- 最大5件のアバターを表示し、それ以上は「+X」で表示
- フォロー通知ではフォローバックボタンが表示される

---

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

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

### 推奨読解順序

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

通知は `Notification` 型で、グループ化後は `NotificationGroup` 型になる。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | notifications.tsx | `apps/activitypub/src/views/notifications/notifications.tsx` | Notification型、NotificationGroup型（23-30行目） |

**読解のコツ**: `groupNotifications` 関数（48-110行目）がグループ化ロジックの中核。時間バケット（24時間）とシーケンスカウンターで同タイプ通知をまとめる。

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | notifications.tsx | `apps/activitypub/src/views/notifications/notifications.tsx` | Notificationsコンポーネント（201-505行目） |

**主要処理フロー**:
1. **202-209行目**: 状態管理（openStates、navigate）
2. **219行目**: `useNotificationsForUser` でデータ取得
3. **221-226行目**: グループ化処理
4. **253-283行目**: 通知クリックハンドラー

#### Step 3: グループ化ロジックを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | notifications.tsx | `apps/activitypub/src/views/notifications/notifications.tsx` | groupNotifications（48-110行目） |

**主要処理フロー**:
- **40-46行目**: `getTimeBucket` - 24時間単位のバケット計算
- **53-62行目**: シーケンスカウンター - タイプ境界でインクリメント
- **68-90行目**: タイプ別のグループキー生成
- **92-107行目**: グループへのactor追加

#### Step 4: 表示コンポーネントを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | notification-item.tsx | `apps/activitypub/src/views/notifications/components/notification-item.tsx` | NotificationItemコンポーネント |
| 4-2 | notification-icon.tsx | `apps/activitypub/src/views/notifications/components/notification-icon.tsx` | NotificationIconコンポーネント |

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

```
Notifications (notifications.tsx)
    │
    ├─ useNotificationsForUser
    │      └─ GET /notifications
    │
    ├─ groupNotifications(notifications)
    │      ├─ getTimeBucket (24時間バケット)
    │      └─ グループキー生成
    │
    ├─ Layout
    │
    └─ notificationGroups.map()
           │
           ├─ NotificationItem
           │      ├─ NotificationItem.Icon (グループ時)
           │      ├─ APAvatar + NotificationIcon (単一時)
           │      │
           │      ├─ NotificationItem.Avatars (グループ時)
           │      │      ├─ APAvatar[] (最大5件)
           │      │      ├─ +X 表示
           │      │      └─ 展開/折りたたみボタン
           │      │
           │      └─ NotificationItem.Content
           │             ├─ NotificationGroupDescription
           │             ├─ 対象コンテンツプレビュー
           │             ├─ FeedItemStats (reply/mention時)
           │             └─ FollowButton
           │
           └─ IntersectionObserver
                  └─ fetchNextPage
```

### データフロー図

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

ページロード ────────▶ useNotificationsForUser
                            │
                            └─ GET /notifications ────────▶ Notification[]
                                   │
                                   └─ groupNotifications
                                          │
                                          ├─ 時間バケット計算
                                          ├─ シーケンスカウンター
                                          └─ タイプ別グループ化
                                                 │
                                                 └─ NotificationGroup[] ─▶ 表示

通知クリック ───────▶ handleNotificationClick
                            │
                            ├─ like/repost ──▶ navigate('/reader|notes/{postId}')
                            ├─ reply/mention ─▶ navigate('/notes/{postId}')
                            └─ follow ────────▶ toggleOpen() or navigate('/profile/{handle}')
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| notifications.tsx | `apps/activitypub/src/views/notifications/notifications.tsx` | ソース | Notificationsメインコンポーネント |
| notification-item.tsx | `apps/activitypub/src/views/notifications/components/notification-item.tsx` | ソース | 通知アイテムコンポーネント |
| notification-icon.tsx | `apps/activitypub/src/views/notifications/components/notification-icon.tsx` | ソース | 通知アイコンコンポーネント |
| use-activity-pub-queries.ts | `apps/activitypub/src/hooks/use-activity-pub-queries.ts` | ソース | ActivityPub APIフック |
| follow-button.tsx | `apps/activitypub/src/components/global/follow-button.tsx` | ソース | フォローボタン |
| routes.tsx | `apps/activitypub/src/routes.tsx` | ソース | ルーティング定義 |
