# 画面設計書 68-探索画面

## 概要

本ドキュメントは、Ghost管理画面における「探索画面（Explore）」の設計仕様を記述したものである。この画面では、フェディバース上のおすすめアカウントをトピック別に閲覧し、新しいアカウントをフォローできる。

### 本画面の処理概要

探索画面は、フェディバース上で新しいアカウントを発見するためのディスカバリービューである。Ghostパブリッシャーや他のソーシャルウェブ上のおすすめアカウントを、トピック別に閲覧できる。

**業務上の目的・背景**：ソーシャルウェブでの活動を活発化させるためには、興味のあるアカウントをフォローし、コンテンツを充実させることが重要である。この画面により、Ghost内外の優良なパブリッシャーやアカウントを効率的に発見でき、フォロワー獲得のための最初のステップとして他者をフォローすることを促進する。

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

**主要な操作・処理内容**：
1. おすすめアカウント一覧の表示
2. トピック別フィルタリング（top/ghost/tech/culture等）
3. アカウントのフォロー/アンフォロー操作
4. プロフィール詳細への遷移
5. 無限スクロールによる追加読み込み

**画面遷移**：サイドバーからアクセス。アカウントクリックでプロフィール画面へ遷移。トピック変更でURLが更新される。

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

## 関連機能

| 機能No | 機能名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 57 | ActivityPub | 主機能 | フェディバースのアカウント・コンテンツ探索 |

## 画面種別

一覧画面（ディスカバリービュー）

## URL/ルーティング

- メインパス: `/ghost/#/activitypub/explore`
- トピック付き: `/ghost/#/activitypub/explore/:topic`
- ページタイトル: 「Explore」

## 入出力項目

### フィルター入力

| 項目名 | データ型 | 入力/出力 | 説明 |
|--------|----------|----------|------|
| トピック | Topic | 入力 | 表示するトピック（top/ghost/tech/culture等） |

## 表示項目

### 説明バナー

| 項目名 | 表示形式 | 説明 |
|--------|----------|------|
| アイコン | Sprout | 成長を示すアイコン |
| タイトル | テキスト | 「The fastest way to grow your followers, is to follow others!」 |
| 説明文 | テキスト | Ghostパブリッシャーやソーシャルウェブからのおすすめ説明 |
| 閉じるボタン | ボタン | バナーを閉じる |

### トピックフィルター

| 項目名 | 表示形式 | 説明 |
|--------|----------|------|
| Top | タブ | 人気のおすすめ |
| Ghost | タブ | Ghostパブリッシャー |
| Tech | タブ | テクノロジートピック |
| Culture | タブ | カルチャートピック |

### アカウントリスト

| 項目名 | 表示形式 | 説明 |
|--------|----------|------|
| アバター | 画像 | アカウントのアバター画像 |
| 名前 | テキスト | アカウント表示名 |
| ハンドル | テキスト | @username@domain形式 |
| バイオ | テキスト | アカウントの自己紹介（2行まで） |
| フォローボタン | ボタン | フォロー/フォロー中 |

## イベント仕様

### 1-トピック切り替え

1. TopicFilterのタブをクリック
2. `onTopicChange` コールバックを実行
3. トピックが「top」の場合：`navigate('/explore')`
4. それ以外の場合：`navigate('/explore/{topic}')`
5. `useExploreProfilesForUserByTopic` が新しいトピックでデータ取得

### 2-アカウントクリック

1. ExploreProfile コンポーネントをクリック
2. `navigate('/profile/{handle}')` を実行
3. プロフィール画面へ遷移

### 3-フォロー操作

1. FollowButton をクリック
2. `onFollow` / `onUnfollow` コールバックを実行
3. `updateExploreProfile` でローカル状態を更新
4. ActivityPub API経由でFollow/Unfollow操作
5. 成功時：ボタン状態を反転
6. 失敗時：エラートースト表示

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

1. IntersectionObserverが `.load-more-trigger` 要素を検知（threshold: 0.1）
2. `hasNextPage` が true かつ `isFetchingNextPage` が false の場合
3. `fetchNextPage` を実行
4. 次ページのアカウントを取得
5. 既存リストに追加表示

### 5-説明バナー閉じる

1. バナーの閉じるボタン（X）をクリック
2. `setExplainerClosed(true)` を実行
3. `isExplainerClosed` フラグが true になり、バナーが非表示に

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

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

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

| 操作（イベント） | 対象 | 操作種別 | 概要 |
|----------------|------|---------|------|
| プロフィール取得 | ActivityPub API | GET | おすすめアカウント一覧の取得 |
| フォロー | ActivityPub API | POST | Follow操作 |
| アンフォロー | ActivityPub API | DELETE | Unfollow操作 |

## メッセージ仕様

| メッセージ種別 | メッセージ内容 | 表示条件 |
|---------------|---------------|----------|
| 情報 | The fastest way to grow your followers, is to follow others! | 説明バナー（未閉じ時） |
| 情報 | Here are some recommendations to get you started... | 説明バナー詳細 |

## 例外処理

| 例外条件 | 処理内容 |
|---------|---------|
| ネットワークエラー | ローディング表示継続 |
| フォロー失敗 | エラートースト表示 |

## 備考

- デフォルトトピックは「top」
- 説明バナーの閉じた状態は `useOnboardingStatus` で管理される
- 「following」トピックはこの画面では除外される（excludeTopics）
- フォロー状態は楽観的更新で即時反映される
- バイオは2行までに制限表示される（line-clamp-2）
- ProfilePreviewHoverCard でホバー時にプロフィールプレビュー表示

---

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

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

### 推奨読解順序

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

探索画面では `ExploreAccount` 型のアカウントデータを扱う。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | activitypub.ts | `apps/activitypub/src/api/activitypub.ts` | ExploreAccount型の定義 |

**読解のコツ**: `ExploreAccount` は `Account` を拡張しており、`followedByMe` プロパティでフォロー状態を管理する。

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

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

**主要処理フロー**:
1. **102行目**: `useOnboardingStatus` で説明バナー状態取得
2. **103-106行目**: URLパラメータからトピック取得（デフォルト: 'top'）
3. **108-109行目**: `useExploreProfilesForUserByTopic` でデータ取得
4. **123-140行目**: IntersectionObserverによる無限スクロール

#### Step 3: プロフィール表示コンポーネントを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | explore.tsx | `apps/activitypub/src/views/explore/explore.tsx` | ExploreProfileコンポーネント（21-99行目） |

**主要処理フロー**:
- **24-25行目**: 現在のユーザー取得（自分自身判定用）
- **28-37行目**: フォロー/アンフォローコールバック
- **42-98行目**: プロフィールカードのレンダリング

#### Step 4: トピックフィルターを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | topic-filter.tsx | `apps/activitypub/src/components/topic-filter.tsx` | TopicFilterコンポーネント |

**主要処理フロー**:
- トピック一覧の表示
- `onTopicChange` コールバックでトピック切り替え
- `excludeTopics` で除外するトピック指定

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

```
Explore (explore.tsx)
    │
    ├─ useOnboardingStatus
    │      └─ isExplainerClosed, setExplainerClosed
    │
    ├─ useParams() → topic取得
    │
    ├─ useExploreProfilesForUserByTopic(topic)
    │      ├─ GET /explore/{topic}
    │      └─ updateExploreProfile
    │
    ├─ Layout
    │
    ├─ 説明バナー（!isExplainerClosed時）
    │      └─ Button (閉じる)
    │
    ├─ TopicFilter
    │      ├─ excludeTopics: ['following']
    │      └─ onTopicChange → navigate('/explore/{topic}')
    │
    ├─ ExploreProfile[] (一覧)
    │      ├─ APAvatar
    │      ├─ 名前・ハンドル
    │      ├─ バイオ
    │      ├─ FollowButton
    │      └─ onClick → navigate('/profile/{handle}')
    │
    └─ IntersectionObserver (.load-more-trigger)
           └─ fetchNextPage
```

### データフロー図

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

ページロード ────────▶ useParams → topic
                            │
                            └─ topic || 'top'
                                   │
                                   └─ useExploreProfilesForUserByTopic
                                          │
                                          └─ GET /explore/{topic}
                                                 │
                                                 └─ ExploreAccount[] ─▶ 表示

トピック切り替え ───▶ onTopicChange(newTopic)
                            │
                            ├─ 'top' ────▶ navigate('/explore')
                            │
                            └─ other ───▶ navigate('/explore/{newTopic}')

フォロー ───────────▶ FollowButton.onClick
                            │
                            ├─ updateExploreProfile (楽観的更新)
                            │
                            └─ POST /follow ───────────▶ 成功: 確定
                                                        失敗: ロールバック
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| explore.tsx | `apps/activitypub/src/views/explore/explore.tsx` | ソース | Exploreメインコンポーネント |
| topic-filter.tsx | `apps/activitypub/src/components/topic-filter.tsx` | ソース | トピックフィルター |
| follow-button.tsx | `apps/activitypub/src/components/global/follow-button.tsx` | ソース | フォローボタン |
| ap-avatar.tsx | `apps/activitypub/src/components/global/ap-avatar.tsx` | ソース | アバターコンポーネント |
| profile-preview-hover-card.tsx | `apps/activitypub/src/components/global/profile-preview-hover-card.tsx` | ソース | プロフィールプレビュー |
| use-activity-pub-queries.ts | `apps/activitypub/src/hooks/use-activity-pub-queries.ts` | ソース | ActivityPub APIフック |
| routes.tsx | `apps/activitypub/src/routes.tsx` | ソース | ルーティング定義 |
